• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

Build Layers tree

Advocate ,
Jul 26, 2015 Jul 26, 2015

Copy link to clipboard

Copied

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

TOPICS
Actions and scripting

Views

556

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe
Community Expert ,
Jul 26, 2015 Jul 26, 2015

Copy link to clipboard

Copied

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

};

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jul 26, 2015 Jul 26, 2015

Copy link to clipboard

Copied

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:

DB_ 2015-07-26 at 16.19.08.png

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jul 26, 2015 Jul 26, 2015

Copy link to clipboard

Copied

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jul 26, 2015 Jul 26, 2015

Copy link to clipboard

Copied

Hi Matias,

the layers.length suggestion is a good one (even if I don't know how much will impact the speed), in fact it's better:

function traverseLayers(layerset, layPath, ftn) { 

    for (var i = 0, len = layerset.layers.length; i < len; 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); 

        } 

    } 

}

Yesterday (very late at) night I didn't set the activeLayer and it didn't work - I'm afraid I don't remember now the reason, I guess it's related to the layerset length in the recursive function but I can easily be wrong.

Alas, I'm null at Generator! It is one of those thing in my ToDo list for a while: besides Tom Krcha tutorials, is there anything new?

Best regards,

-Davide

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Enthusiast ,
Jul 26, 2015 Jul 26, 2015

Copy link to clipboard

Copied

The layers.length was the biggest single optimization I found in my code in a quick optimization. Also using .layers instead of .artLayers and filtering in your code was noticeable.

For generator it's not a huge effort to get the basic stuff going (~reading document info). I think I had Tom's sample running in a day or so (see here for some problems encountered Generator guidelines/howto/getting started questions). Harder part was trying to keep 100% in sync with document updates as the delta info has a few holes in it and you end up doing workarounds (see Google Groups for some of my reports). But doing e.g. a web server for fetching doc details to be used in panel should not be big effort and it allows you to do some really cool things. For example my generator plugin keeps track of the selected doc&layer and offers this info with layer detail data in 127.0.0.1:8080/selection that I poll in the panel every ~.5 sec updating the panel content.

Not saying it's an immediate solution to your problems, but considering how much time you spend on scripting you'll probably have to bite the bullet at some point

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jul 27, 2015 Jul 27, 2015

Copy link to clipboard

Copied

LATEST

Matias,

you should really blogpost about a localhost webserver talking to and/or getting info from PS via Generator!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jul 27, 2015 Jul 27, 2015

Copy link to clipboard

Copied

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

Hmm, been a while since I attempted that Scripting approach …

First theArray is the overall Array that contains the result.

Whenever a layerSectionStart is hit aNumber is increased by one to keep track of how deep in one gets group-wise and the group’s specifics (name and ID) are added to setArray.

On the other hand if a layerSectionEnd is hit aNumber is reduced by one (edited) and setArray as shortened accordingly.

If a layer is neither layerSectionStart nor layerSectionEnd an Array of its specifics is added to theArray with the current content of setArray added.

I guess one could restructure the procedure to get a list of groups and their content instead of a list of layers and their parents.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines