Script to delete all empty folders & layers

Anyone, especially newbies, asking for help with Photoshop Scripting and Photoshop Automation - as opposed to those contributing to discussion about an aspect of Photoshop Scripting

Moderators: Tom, Kukurykus

mycort

Script to delete all empty folders & layers

Post by mycort »

Hopefully this script isn't too hard...it will basically deletes all empty layers and folders in the layers pallete....

Professional AI Audio Generation within Adobe Premiere Pro - Download Free Plugin here

pfaffenbichler

Script to delete all empty folders & layers

Post by pfaffenbichler »

Probably not as fast as with ActionDescriptor-code, but you could give it a try.
Code: Select all// remove empty layers;
// 2012, use it your own risk;
#target photoshop;
if (app.documents.length) {
var myDocument = app.activeDocument;
var theEmptyLayers = collectEmptyLayers(myDocument, []);
for (var m = 0; m < theEmptyLayers.length; m++) {
   theEmptyLayers[m].remove()
   }
};
////// function collect all empty layers //////
function collectEmptyLayers (theParent, allLayers) {
   if (!allLayers) {var allLayers = new Array}
   else {};
   for (var m = theParent.layers.length - 1; m >= 0;m--) {
      var theLayer = theParent.layers[m];
      var bounds = theLayer.bounds;
      var check = false;
      if (Number(bounds[0]) == 0 && Number(bounds[1]) == 0 && Number(bounds[2]) == 0 && Number(bounds[3]) == 0) {check = true};
      if (theLayer.typename == "ArtLayer") {
         if (check == true) {         
            allLayers.push(theLayer)
            }
         }
// apply the function to layersets;
      else {
         allLayers = (collectEmptyLayers(theLayer, allLayers))
         if (check == true) {
            allLayers.push(theLayer);
            }
         }
      };
   return allLayers
   };

Edit: To elaborate: I used the Layers’ bounds to determine whether they have content or not.
With LayerSets one could (or maybe should) use the length of the Layers property to determine whether a Group contains content, but in a test the bounds seemed to work out.
mycort

Script to delete all empty folders & layers

Post by mycort »

Works great.......but when I apply to a more complex psd fle with hundreds of layers, then it takes forever to finish, after 40mins it still going....had to force quit because it took to long.....anyway to optimize and make this more efficient when reading big layer structures?
Paul MR

Script to delete all empty folders & layers

Post by Paul MR »

This might be a bit faster....

Code: Select allmain();
function main(){
if(!documents.length) return;
var layerInfo = getLayerInfo();
for(var a in layerInfo){//delete blank layers
    if(layerInfo[a][2] == 'false') deleteLayerByID(Number(layerInfo[a][0]));
    }
for(var z in layerInfo){
     if(layerInfo[z][2] == 'true'){//delete empty layerSets
         selectLayerById(Number(layerInfo[z][0]));
         if(activeDocument.activeLayer.layers.length == 0) deleteLayerByID(Number(layerInfo[z][0]));
         }
    }
};
function deleteLayerByID(ID) {
    var desc = new ActionDescriptor();
        var ref = new ActionReference();
        ref.putIdentifier(charIDToTypeID('Lyr '), ID);
    desc.putReference( charIDToTypeID('null'), ref );
    executeAction( charIDToTypeID('Dlt '), desc, DialogModes.NO );
};
function selectLayerById(ID, add) {
    add = (add == undefined)  ? add = false : add;
   var ref = new ActionReference();
   ref.putIdentifier(charIDToTypeID('Lyr '), ID);
   var desc = new ActionDescriptor();
   desc.putReference(charIDToTypeID('null'), ref);
   if (add) {
      desc.putEnumerated(stringIDToTypeID('selectionModifier'), stringIDToTypeID('selectionModifierType'), stringIDToTypeID('addToSelection'));
   }
   desc.putBoolean(charIDToTypeID('MkVs'), false);
   executeAction(charIDToTypeID('slct'), desc, DialogModes.NO);
}
function getLayerInfo(){
   var ref = new ActionReference();
   ref.putEnumerated( charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') );
   var count = executeActionGet(ref).getInteger(charIDToTypeID('NmbL')) +1;
   var Names=[];
try{
    activeDocument.backgroundLayer;
var i = 0; }catch(e){ var i = 1; };
   for(i;i<count;i++){
       if(i == 0) continue;
        ref = new ActionReference();
        ref.putIndex( charIDToTypeID( 'Lyr ' ), i );
        var desc = executeActionGet(ref);
        var layerName = desc.getString(charIDToTypeID( 'Nm  ' ));
        var Id = desc.getInteger(stringIDToTypeID( 'layerID' ));
        if(layerName.match(/^<\/Layer group/) ) continue;
        var layerType = typeIDToStringID(desc.getEnumerationValue( stringIDToTypeID( 'layerSection' )));
        var isLayerSet =( layerType == 'layerSectionContent') ? false:true;
        var boundsDesc = desc.getObjectValue(stringIDToTypeID( "bounds" ));
if(boundsDesc.getUnitDoubleValue(stringIDToTypeID('right')) - boundsDesc.getUnitDoubleValue(stringIDToTypeID('left')) == 0){
    Names.push([[Id],[layerName],[isLayerSet]]);
    }
if(isLayerSet) Names.push([[Id],[layerName],[isLayerSet]]);
   };
return Names;
};
mycort

Script to delete all empty folders & layers

Post by mycort »

definitely much faster....big difference..thx alot!
pfaffenbichler

Script to delete all empty folders & layers

Post by pfaffenbichler »

Great Script, Paul!
Checking only two of the bounds-values against each other makes sense and is shorter than checking all four.

I still struggle with the peculiar »nature« of LayerSets in Action Manager code … groups seem to be peculiar beasts in Photoshop files indeed.
Paul MR

Script to delete all empty folders & layers

Post by Paul MR »

Yes Christoph, I am struggling with layersets, here is some code I have been playing with, it might of of use to someone
To be run from ESTK and info sent to the console.

Code: Select all#target photoshop
app.bringToFront();
main();
function main(){
//Photoshop CS3 or better required
if(app.version.match(/^\d+/) < 10) return;
if(!documents.length) return;
var doc = activeDocument;
//Get all layer information
var allLayers =getNamesPlusIDs();
if(allLayers.length <2) return;
var lSets=new Array();
//Get a list of all layerSets
for(var l in allLayers){ if(allLayers[l][2] == 'true') lSets.push(allLayers[l]);}
//Create dynamic variables to store layerSet information
var LayerSetContent = new Object();
for (var i in lSets){ LayerSetContent[lSets[0]] = new Array();}
//Obtain a list of layers in each layerSet
for (var v in lSets){
//Select each layerSet in turn
selectLayerById(Number(lSets[v][0]));
//Remove selected layerSet
doc.activeLayer.remove();
//Get a list of remaining layers
var listNow =getNamesPlusIDs();
//Restore layerSet
doc.activeHistoryState = doc.historyStates[doc.historyStates.length-2];
//Get list of layerSet layers
var lList = returnArrayDif(allLayers,listNow);
//Write each layerset details to dynamic variables
for(var ln in lList){
    if(lSets[v].toString() != lList[ln].toString()) LayerSetContent[lSets[v][0]].push(lList[ln]);
        }
    }
//Access the information
//does not include a background layer if one exists
$.writeln('Total layers = ' + allLayers.length);
$.writeln('Total layerSets = ' + lSets.length);
$.writeln('Nested layerSets = ' +(lSets.length - doc.layerSets.length));
$.writeln('***********');
for (var k in lSets){
$.writeln(lSets[k][1].toString() + ' LayerSetID = ' + lSets[k][0].toString());
for (var t in LayerSetContent[lSets[k][0]]){
    $.writeln('ID = ' +  LayerSetContent[lSets[k][0]][t][0] + ' Name  = ' + LayerSetContent[lSets[k][0]][t][1] + ' Is LayerSet = '  + LayerSetContent[lSets[k][0]][t][2] + ' Visible = '  + LayerSetContent[lSets[k][0]][t][3]);
    }
$.writeln('------------------');
    }
};

function getNamesPlusIDs(){
   var ref = new ActionReference();
   ref.putEnumerated( charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') );
   var count = executeActionGet(ref).getInteger(charIDToTypeID('NmbL')) +1;
   var Names=[];
try{
    activeDocument.backgroundLayer;
var i = 0; }catch(e){ var i = 1; };
   for(i;i<count;i++){
       if(i == 0) continue;
        ref = new ActionReference();
        ref.putIndex( charIDToTypeID( 'Lyr ' ), i );
        var desc = executeActionGet(ref);
        var layerName = desc.getString(charIDToTypeID( 'Nm  ' ));
        var Id = desc.getInteger(stringIDToTypeID( 'layerID' ));
        var vis = desc.getBoolean(charIDToTypeID( 'Vsbl' ));
        if(layerName.match(/^<\/Layer group/) ) continue;
        var layerType = typeIDToStringID(desc.getEnumerationValue( stringIDToTypeID( 'layerSection' )));
        var isLayerSet =( layerType == 'layerSectionContent') ? false:true;
Names.push([[Id],[layerName],[isLayerSet],[vis]]);
   }
return Names;
};
function selectLayerById(ID, add) {
    add = (add == undefined)  ? add = false : add;
   var ref = new ActionReference();
   ref.putIdentifier(charIDToTypeID('Lyr '), ID);
   var desc = new ActionDescriptor();
   desc.putReference(charIDToTypeID('null'), ref);
   if (add) {
      desc.putEnumerated(stringIDToTypeID('selectionModifier'), stringIDToTypeID('selectionModifierType'), stringIDToTypeID('addToSelection'));
   }
   desc.putBoolean(charIDToTypeID('MkVs'), false);
   executeAction(charIDToTypeID('slct'), desc, DialogModes.NO);
};
function returnArrayDif(array1,array2) { 
var returnArray=[];
var idx=false;
for(var a in array1){
    for(var b in array2){
        if(array1[a].toString() == array2.toString()) idx=true;
        }
    if(!idx) returnArray.push(array1[a]);
    idx=false;
    }
return returnArray;
};

pfaffenbichler

Script to delete all empty folders & layers

Post by pfaffenbichler »

Thank you.
That is quite some code.
kostyanet

Script to delete all empty folders & layers

Post by kostyanet »

It looks like a very simple solution to hide all empty layers and then delete it simultaneously, but checking by layer.bounds works slowly on complex document. Is there a way for PS CS2 to check it faster?

Code here:

Code: Select allcTID = function(s) { return cTID[s] || cTID[s] = app.charIDToTypeID(s); };
sTID = function(s) { return sTID[s] || sTID[s] = app.stringIDToTypeID(s); };

var doc = app.activeDocument;
var hidden = revLayers(doc, new Array());
delHidden(hidden);

// recursive search for empty layer and hide it
function revLayers(obj, res) {
   var layers = obj.layers;
   for (var i = 0; i < layers.length; i++) {
      var layer = layers;
      if(!layer.visible) res.push(layer); // save hidden layer
      layer.visible = checkBounds(layer); // hide empty layer
      if(layer.visible && layer.layers) res = revLayers(layer, res); // check non empty set
   }
   return res;
}

// I have asked about this function above
function checkBounds(layer) {
   var bnd = layer.bounds;
   var res = bnd[0]+bnd[1]+bnd[2]+bnd[3];
   return (res.value>0);
}

function delHidden(layers) {
    var desc = new ActionDescriptor();
    var ref = new ActionReference();
    ref.putEnumerated(cTID("Lyr "), cTID("Ordn"), sTID("hidden"));
    desc.putReference( cTID( "null" ), ref );
   executeAction(cTID("Dlt "), desc, DialogModes.NO );
   
   // hide originally hidden non-empty layers; ignore errors for deleted ones
   for (var i = 0; i < layers.length; i++) {
      try {layers.visible = false; } catch (e) { continue; }
   }   
}
Paul MR

Script to delete all empty folders & layers

Post by Paul MR »

Yes you should be able to use AM code as a layer has the following keys with Photoshop CS2
AD 01 = name : DescValueType.STRINGTYPE
AD 02 = color : DescValueType.ENUMERATEDTYPE
AD 03 = visible : DescValueType.BOOLEANTYPE
AD 04 = mode : DescValueType.ENUMERATEDTYPE
AD 05 = opacity : DescValueType.INTEGERTYPE
AD 06 = layerID : DescValueType.INTEGERTYPE
AD 07 = itemIndex : DescValueType.INTEGERTYPE
AD 08 = count : DescValueType.INTEGERTYPE
AD 09 = preserveTransparency : DescValueType.BOOLEANTYPE
AD 10 = layerFXVisible : DescValueType.BOOLEANTYPE
AD 11 = globalAngle : DescValueType.INTEGERTYPE
AD 12 = background : DescValueType.BOOLEANTYPE
AD 13 = layerSection : DescValueType.ENUMERATEDTYPE
AD 14 = layerLocking : DescValueType.OBJECTTYPE
AD 15 = group : DescValueType.BOOLEANTYPE
AD 16 = targetChannels : DescValueType.LISTTYPE
AD 17 = visibleChannels : DescValueType.LISTTYPE
AD 18 = channelRestrictions : DescValueType.LISTTYPE
AD 19 = fillOpacity : DescValueType.INTEGERTYPE
AD 20 = bounds : DescValueType.OBJECTTYPE

and here is a code example to get various details including bounds of all layers...
Code: Select allfunction getNamesPlusIDs(){
   var ref = new ActionReference();
   ref.putEnumerated( charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') );
   var count = executeActionGet(ref).getInteger(charIDToTypeID('NmbL')) +1;
   var Names=[];
   var doc = activeDocument;
if(doc.layers[doc.layers.length-1].isBackgroundLayer){
var i = 0; }else{ var i = 1; };
   for(i;i<count;i++){
       if(i == 0) continue;
        ref = new ActionReference();
        ref.putIndex( charIDToTypeID( 'Lyr ' ), i );
        var desc = executeActionGet(ref);
        var layerName = desc.getString(charIDToTypeID( 'Nm  ' ));
        var Id = desc.getInteger(stringIDToTypeID( 'layerID' ));
        if(layerName.match(/^<\/Layer group/) ) continue;
        var layerType = typeIDToStringID(desc.getEnumerationValue( stringIDToTypeID( 'layerSection' )));
        var isLayerSet =( layerType == 'layerSectionContent') ? false:true;
        var descBounds = desc.getObjectValue(stringIDToTypeID( "bounds" ));
var Left = descBounds.getUnitDoubleValue(stringIDToTypeID('left'));
var Top = descBounds.getUnitDoubleValue(stringIDToTypeID('top'));
var Right = descBounds.getUnitDoubleValue(stringIDToTypeID('right'));
var Bottom = descBounds.getUnitDoubleValue(stringIDToTypeID('bottom'));

Names.push([[Id],[layerName],[isLayerSet],[Left],[Top],[Right],[Bottom]]);
   };
return Names;
};

var layerInfo = getNamesPlusIDs();
for(var a in layerInfo){
$.writeln(layerInfo[a]);
}