Photoshop Script to delete unused layers

Participant ,
Oct 27, 2021 Oct 27, 2021

Copy link to clipboard

Copied

Hi all,

I have tried the below code to delete the unused layers in the layerset and artlayers, but the code taking too much time delete the unused layers if the psd file containing lot of layers & layerset. Could anyone please help to get rid of this?

 

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
   };

 

TOPICS
Actions and scripting, macOS

Views

43

Likes

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 Professional ,
Oct 27, 2021 Oct 27, 2021

Copy link to clipboard

Copied

LATEST

 

 

By unused layer do you mean layer with their visibility off? Photoshop has a Script to delete all empty layers.

// Copyright: 2018 Adobe Systems, Inc. All rights reserved.
// Completely rewritten by Jaroslav Bereza <http://bereza.cz> previous script written by Naoki Hada
// Visit http://bereza.cz/ps for more scripts
/*
@@@BUILDINFO@@@ Delete All Empty Layers.jsx 2.0.0.0
*/
/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING
<javascriptresource>
<name>$$$/JavaScripts/DeleteAllEmptyLayers/Menu=Delete All Empty Layers</name>
<category>Delete</category>
<enableinfo>true</enableinfo>
<eventid>a0754df2-9c60-4b64-a940-6a2bb1102652</eventid>
<terminology><![CDATA[<< /Version 1 
                         /Events << 
                          /a0754df2-9c60-4b64-a940-6a2bb1102652 [($$$/JavaScripts/DeleteAllEmptyLayers/Menu=Delete All Empty Layers) /noDirectParam <<
                          >>] 
                         >> 
                      >> ]]></terminology>
</javascriptresource>
// END__HARVEST_EXCEPTION_ZSTRING
*/
// enable double clicking from the 
// Macintosh Finder or the Windows Explorer
#target Photoshop
// Make Photoshop the frontmost application
app.bringToFront();
// debug level: 0-2 (0:disable, 1:break on error, 2:break at beginning)
// $.level = 2;
// debugger; // launch debugger on next line
/*
    KNOWN ISSUES:
    - if you make set visibility of clipped masks to hidden and you run "undo" command then these layers stay hidden
*/
/////////////////////////
// SETUP
/////////////////////////
// all the strings that need localized
var strDeleteAllEmptyLayersHistoryStepName = localize("$$$/JavaScripts/DeleteAllEmptyLayers/Menu=Delete All Empty Layers");
/////////////////////////
// MAIN
/////////////////////////
var doc;  // remember the document. But we do it later because we first need make sure that there are documents
var numberOfLayers;
var backgroundCounter;
var isCancelled = false;
// caching precalculated typeID numbers for saving a bit miliseconds and nicer code
var TID = {
    property: charIDToTypeID("Prpr"),
    bounds: stringIDToTypeID("bounds"),
    layer: charIDToTypeID("Lyr "),
    top: stringIDToTypeID('top'),
    bottom: stringIDToTypeID('bottom'),
    left: stringIDToTypeID('left'),
    right: stringIDToTypeID('right'),
    layerLocking: stringIDToTypeID("layerLocking"),
    protectAll: stringIDToTypeID('protectAll'),
    layerID: stringIDToTypeID("layerID"),
    group: stringIDToTypeID("group"),
    layerSection: stringIDToTypeID("layerSection"),
    textKey: stringIDToTypeID("textKey"),
    idNull: charIDToTypeID("null"),
    idDelete: charIDToTypeID("Dlt "),
    document: charIDToTypeID("Dcmn"),
    ordinal: charIDToTypeID("Ordn"),
    target: charIDToTypeID("Trgt"),
    hide: charIDToTypeID("Hd  "),
    application: charIDToTypeID("capp"),
    set: charIDToTypeID("setd"),
    to: charIDToTypeID("T   "),
    playbackOptions: stringIDToTypeID("playbackOptions"),
    hasBackgroundLayer: stringIDToTypeID("hasBackgroundLayer"),
    performance: stringIDToTypeID("performance"),
    layerSectionEnd: stringIDToTypeID("layerSectionEnd"),
    layerSectionStart: stringIDToTypeID("layerSectionStart"),
    layerSectionContent: stringIDToTypeID("layerSectionContent"),
    numberOfLayers: stringIDToTypeID("numberOfLayers"),
    accelerated: stringIDToTypeID("accelerated")
};
main();
// Record the script in the Actions palette when recording an action
try {
    var playbackDescription = new ActionDescriptor();
    var playbackReference = new ActionReference();
    playbackReference.putEnumerated(TID.document, TID.ordinal, TID.target);
    playbackDescription.putReference(TID.idNull, playbackReference);
    app.playbackDisplayDialogs = DialogModes.NO;
    app.playbackParameters = playbackDescription;
} catch (e) { /* do nothing */ }
isCancelled ? 'cancel' : undefined; // quit, returning 'cancel' (don't localize) makes the actions palette not record our script
/////////////////////////
// FUNCTIONS
/////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Function: main
// Usage: container function to hold all the working code that generates history states
// Input: <none> Must have an open document
// Return: <none>
///////////////////////////////////////////////////////////////////////////////
function main() {
    // there must be document
    // Document must have at least one layer so we don't need rum script if there is only one layer
    if (app.documents.length > 0) {
        numberOfLayers = getNumberOfLayers();
        backgroundCounter = getBackgroundLayerCounter();
        if(numberOfLayers + backgroundCounter > 1){
            try {
                doc = app.activeDocument;
                doc.suspendHistory(strDeleteAllEmptyLayersHistoryStepName, "runTask()");
            } catch (e) {
                isCancelled = true;
            }
        }
    }
}
function runTask() {
    acceleratePlayback();
    var deleteLayersList = [];
    var hideLayersList = [];
    var layers = new Array(numberOfLayers); // we know array lenght so we can reserve fixed space in memory
    var maxNestedLevels = 0;
    // We want to avoid DOM code here because it can be slow if document has a lot layers and nested layerSets. So we will use Action Manager code.
    for (var layerIndex = numberOfLayers, stepsInside = 0; layerIndex > 0; layerIndex--) { // stepsInside = how deep I am in folder structure
        var locked = getIsLocked(layerIndex);
        var layerType = getLayerType(layerIndex);
        var shouldRemove = false;
        var nestedLevels;
        // NOT layerSet
        if (layerType === 'layer') {
            nestedLevels = stepsInside + 1;
            shouldRemove = ((hasZeroDimensions(layerIndex) || getIsEmptyTextLayer(layerIndex)) && !locked);
        }
        // layerSet end - closing (invisible) layer
        else if (layerType === 'endOfLayerSet') {
            nestedLevels = stepsInside;
            stepsInside--;
        }
        // layerSet start - opening layer
        else if (layerType === 'startOfLayerSet') {
            stepsInside++;
            nestedLevels = stepsInside;
            shouldRemove = true; // we will check it later properly
        }
        if (nestedLevels > maxNestedLevels) {
            maxNestedLevels = nestedLevels;
        }
        var layerInfo = {
            nestedLevels: nestedLevels,
            layerType: layerType,
            itemIndex: layerIndex,
            itemID: getLayerId(layerIndex),
            remove: shouldRemove,
            locked: locked,
            isClipped: getIsClipped(layerIndex)
        };
        layers[numberOfLayers - layerIndex] = layerInfo;
    }
    resolveLayerSetsWithContent();
    resolveChildsOfLockedLayerSets();
    resolveClippingMasks();
    addLayersToDeleteLayersList();
    if (deleteLayersList && deleteLayersList.length) { // if there is something to delete
        // if layer which we want delete has clipping mask, then we hide clipping mask layers
        if (hideLayersList && hideLayersList.length) {runHideLayers(hideLayersList);}
        runDeleteLayers(deleteLayersList);
    }
    
    // we traverse layers from most nested levels to more shallow levels
    // so we can tell parent layerSets that we don't want remove them because they will have content
    // we traverse all layers in document for each level. Maximum nested levels is 10 and maximum layers is 8000. This means 80 000 cycles in extreme case.
    function resolveLayerSetsWithContent(){
        for (var j = 1; j < maxNestedLevels; maxNestedLevels--) {
            for (var i = 0; i < numberOfLayers; i++) {
                var layer = layers[i];
                
                if (layer.nestedLevels === maxNestedLevels) {
                    if(
                        // don't remove parent layerSet if we want keep its content
                        (layer.layerType === 'layer' && !layer.remove) ||
                        // or don't remove locked parent group or don't remove parent folder if children folder shouldn't be deleted
                        ((layer.locked || !layer.remove) && layer.layerType === 'startOfLayerSet')
                    ){ 
                        var parrentLayerSet = layers[getParentLayerSet(i)];
                        parrentLayerSet.remove = false;
                    }
                }
            }
        }
    }
    
    // excludes all childs from delete list if parent layerSet is locked
    function resolveChildsOfLockedLayerSets() {
        for (var j = 0; j < numberOfLayers; j++) { 
            //var layer = layers[j];
            if (layers[j].locked && layers[j].layerType === 'startOfLayerSet') {
                var initialNestedLevel = layers[j].nestedLevels;
                j++;
                while (initialNestedLevel < layers[j].nestedLevels) {
                    layers[j].remove = false;
                    j++;
                }
            }
        }
    }
    // don't mess up clipping mask layers - if we remove layer, then clipped layers might become visible so we hide them
    function resolveClippingMasks(){
        for (var j = 0; j < numberOfLayers; j++) { 
            var layer = layers[j];
            if (layer.isClipped) {
                var tempList = []; // here we store all clipped layers indexes
                while (layer.isClipped) { // this goes from top layer to bottom layer in layers panel and layers clipped to nothing are impossible
                    tempList.push(layer.itemIndex);
                    layer = layers[++j];
                }
                if (layer.remove && !layer.locked) {
                    hideLayersList = hideLayersList.concat(tempList);
                }
            }
        }       
    }
    // just move layer from one list to another list
    function addLayersToDeleteLayersList() {
        for (var j = 0; j < numberOfLayers; j++) {
            var layer = layers[j];
            if (layer.remove && !layer.locked) {
                deleteLayersList.push(layer.itemID);
            }
        }
    }
    // this will find parental layerSet for current layer or layerSet
    function getParentLayerSet(p) { 
        for (var i = p - 1; i > 0; i--) {
            if (layers[i].nestedLevels === layers[p].nestedLevels - 1) {
                return i;
            }
        }
        return 0;
    }
}
//////////////////////////////
// READ DOCUMENT PROPERTIES
//////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Function: hasZeroDimensions
// Usage: we read layer dimensions and if are zero then layer has zero visible pixels (they might be hidden with vector or bitmap mask)
// Input: index of desired layer
// Return: Boolean
///////////////////////////////////////////////////////////////////////////////
function hasZeroDimensions(index) {
    var desc = getLayerPropertyDescriptor(index, TID.bounds);
    var bounds = desc.getObjectValue(TID.bounds);
    var left = bounds.getDouble(TID.left);
    var right = bounds.getDouble(TID.right);
    var top = bounds.getDouble(TID.top);
    var bottom = bounds.getDouble(TID.bottom);
    var result = (left === right) && (top === bottom);
    return result;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getIsClipped
// Usage: we don't want remove locked layers. There are multiple kinds of locks. We need only "protectAll"
// Input: index of desired layer
// Return: Boolean
///////////////////////////////////////////////////////////////////////////////
function getIsLocked(index) {
    var desc = getLayerPropertyDescriptor(index, TID.layerLocking);
    var descLocking = desc.getObjectValue(TID.layerLocking);
    var locked = descLocking.getBoolean(TID.protectAll);
    return locked;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getIsClipped
// Usage: returns ID of layer so we can target layer no matter of layer position in layers panel
// Input: index of desired layer
// Return: Integer
///////////////////////////////////////////////////////////////////////////////
function getLayerId(index) {
    var desc = getLayerPropertyDescriptor(index, TID.layerID);
    var id = desc.getInteger(TID.layerID);
    return id;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getIsClipped
// Usage: returns true if layer is clipping mask
// Input: index of desired layer
// Return: Boolean
///////////////////////////////////////////////////////////////////////////////
function getIsClipped(index) {
    var desc = getLayerPropertyDescriptor(index, TID.group);
    var group = desc.getBoolean(TID.group);
    return group;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getLayerType
// Usage: returns type of layer
// Input: index of desired layer
// Return: String
///////////////////////////////////////////////////////////////////////////////
function getLayerType(index) {
    var desc = getLayerPropertyDescriptor(index, TID.layerSection);
    var type = desc.getEnumerationValue(TID.layerSection);
    switch (type) {
        case TID.layerSectionEnd:
            return 'endOfLayerSet';
        case TID.layerSectionStart:
            return 'startOfLayerSet';
        case TID.layerSectionContent:
            return 'layer';
        default:
            return undefined;
    }
}
///////////////////////////////////////////////////////////////////////////////
// Function: getNumberOfLayers
// Usage: returns number of all layers in document
// Input: <none> Must have an open document
// Return: Integer
///////////////////////////////////////////////////////////////////////////////
function getNumberOfLayers() {
    var desc = getDocumentPropertyDescriptor(TID.numberOfLayers);
    var numberOfLayers = desc.getInteger(TID.numberOfLayers);
    return numberOfLayers;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getIsEmptyTextLayer
// Usage: returns number of all layers in document
// Input: index of desired layer
// Return: Boolean
///////////////////////////////////////////////////////////////////////////////
function getIsEmptyTextLayer(index) {
    var textKey = getLayerPropertyDescriptor(index, TID.textKey);
    
    var isTextLayer = textKey.hasKey(TID.textKey);
    if (!isTextLayer) {
        return false;
    }
    var contentString = textKey.getObjectValue(TID.textKey).getString(TID.textKey);
    var result = (contentString === "");
    return result;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getBackgroundLayerCounter
// Usage: returns if document has background layer
// Input: <none> Must have an open document
// Return: 1 or 0
///////////////////////////////////////////////////////////////////////////////
function getBackgroundLayerCounter() {
    var desc = getDocumentPropertyDescriptor(TID.hasBackgroundLayer);
    var result = Number(desc.getBoolean(TID.hasBackgroundLayer));
    return result;
}
/////////////
// UTILITY
/////////////
///////////////////////////////////////////////////////////////////////////////
// Function: getLayerPropertyDescriptor
// Usage: shortcut helper function which return info about layer property
// Input: Index of desired layer, typeID of desired property
// Return: ActionDescriptor
///////////////////////////////////////////////////////////////////////////////
function getLayerPropertyDescriptor(index, property) {
    var ref = new ActionReference();
    ref.putProperty(TID.property, property);
    ref.putIndex(TID.layer, index);
    var desc = executeActionGet(ref);
    return desc;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getDocumentPropertyDescriptor
// Usage: shortcut helper function which return info about current document property
// Input: TypeID of desired property
// Return: ActionDescriptor
///////////////////////////////////////////////////////////////////////////////
function getDocumentPropertyDescriptor(property) {
    var ref = new ActionReference();
    ref.putProperty(TID.property, property);
    ref.putEnumerated(TID.document, TID.ordinal, TID.target);
    var desc = executeActionGet(ref);
    return desc;
}
///////////////
// ACTIONS
///////////////
///////////////////////////////////////////////////////////////////////////////
// Function: runHideLayers
// Usage: sets visibility of multiple layers to hidden
// Input: Array of layerIndexes
// Return: undefined
///////////////////////////////////////////////////////////////////////////////
function runHideLayers(hideLayersList) {
    var desc = new ActionDescriptor();
    var list = new ActionList();
    for (var i = 0, len = hideLayersList.length; i < len; i++) {
        var ref = new ActionReference();
        ref.putIndex(TID.layer, hideLayersList[i]);
        list.putReference(ref);
    }
    desc.putList(TID.idNull, list);
    executeAction(TID.hide, desc, DialogModes.NO);
}
///////////////////////////////////////////////////////////////////////////////
// Function: runDeleteLayers
// Usage: deletes multiple layers one by one accoring layerID
// Input: Array of layerIDs
// Return: undefined
///////////////////////////////////////////////////////////////////////////////
function runDeleteLayers (list) {
    var desc = new ActionDescriptor();
    var layerRef = new ActionReference();
    for (var i = list.length - 1, len = i; i >= 0; i--) {
        layerRef.putIdentifier(TID.layer, list[i]);
    }
    desc.putReference(TID.idNull, layerRef);
    // "Try" because document must have at least one layer ...what if all layers would be empty?
    // And single layerSet always has 2 layers so it's a bit complicated
    try{
        executeAction(TID.idDelete, desc, DialogModes.NO);
    }catch(e){
        runDeleteLayersFallBack(list);
    }
}
function runDeleteLayersFallBack(list) {
    for (var i = list.length - 1, len = i; i >= 0; i--) {
        var desc = new ActionDescriptor();
        var layerRef = new ActionReference();
        layerRef.putIdentifier(TID.layer, list[i]);
        desc.putReference(TID.idNull, layerRef);
        // Try because document must have at least one layer ...what if all layers would be empty?
        // And single layerSet always has 2 layers so it's a bit complicated
        try{
            executeAction(TID.idDelete, desc, DialogModes.NO);
        }catch(e){
            /* do nothing */
        }
    }
}
///////////////////////////////////////////////////////////////////////////////
// Function: acceleratePlayback
// Usage: in action preferences can be set delay between each action so we make sure that there is no delay
// Input: <none>
// Return: undefined
///////////////////////////////////////////////////////////////////////////////
function acceleratePlayback() {
    var desc = new ActionDescriptor();
    var ref = new ActionReference();
    var desc2 = new ActionDescriptor();
    ref.putProperty(TID.property, TID.playbackOptions);
    ref.putEnumerated(TID.application, TID.ordinal, TID.target);
    desc.putReference(TID.idNull, ref);
    desc2.putEnumerated(TID.performance, TID.performance, TID.accelerated);
    desc.putObject(TID.to, TID.playbackOptions, desc2);
    executeAction(TID.set, desc, DialogModes.NO);
}
// End Delete All Empty Layers.jsx
JJMack

Likes

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