Skip to main content
Participating Frequently
August 17, 2021
Question

Selecting all smart objects outside canvas?

  • August 17, 2021
  • 3 replies
  • 3141 views

Is there a quick and easy way to select/delete all smart objects in a file outside the canvas? I know cropping deletes pixels in a rasterized layer, but it does not affect smart objects. I also know that I can hit "reveal all" in a file, but with the sheer size of the documents I'm working in, doing so creates another aggravating wait time since photoshop has to load all of them visually. 

Basically, is there a way to crop a canvas, then select/delete all the smart objects that are entirely off the canvas without doing reveal all? It would save me so much time. Thank you in advance!

This topic has been closed for replies.

3 replies

Brainiac
August 20, 2021

Since it turned out that my workflow includes such situations, I decided to slightly expand the capabilities of the script, now it:
1. deletes ALL layers whose borders are completely outside the canvas.
2. if after deleting layers empty groups appeared in the document, then it deletes them too

The script tries to find the smallest bounding box in order to take into account (or vice versa, exclude) the effect of the mask (if applied). Also, the script tries to remove the locked layers (and then restore the lock, if after deleting these layers remained inside the canvas)

3. Fixed artboards support

 

EDIT 3:

 

#target photoshop

activeDocument.suspendHistory('Remove layers', 'removeLayers()')

function removeLayers() {
    var doc = new AM('document'),
        lr = new AM('layer'),
        lrs = getLayersCollection(doc, lr);

    parseLayers(lrs, layers = [], canvas = [], locked = [], lr)

    if (layers.length) {
        if (!canvas.length) {
            var res = doc.getProperty('resolution')
            canvas = [{ top: 0, left: 0, right: doc.getProperty('width') * res / 72, bottom: doc.getProperty('height') * res / 72, }]
        }

        var layersToDelete = []
        do {
            var intersected = false,
                cur = layers.shift();
            for (var i = 0; i < canvas.length; i++) {
                if (intersect(cur.bounds, canvas[i]) && !(!cur.bounds.top && !cur.bounds.right && !cur.bounds.left && !cur.bounds.right)) { intersected = true; break; }
            }
            if (!intersected) layersToDelete.push(cur.id)
        } while (layers.length)

        if (layersToDelete.length) {
            doc.deleteLayers(layersToDelete)
            lrs = getLayersCollection(doc, lr)
            var groupsToDelete = []
            cleanupEmptyGroups(lrs, groupsToDelete)
            if (groupsToDelete.length) doc.deleteLayers(groupsToDelete)
        }

        if (locked.length) {
            do {
                var cur = locked.shift()
                try { lr.applyLocking(cur.id, cur.layerLocking) } catch (e) { }
            } while (locked.length)
        }
    }
}

function intersect(lrA, lrB) {
    return (lrB.right < lrA.left || lrB.left > lrA.right) || (lrB.top > lrA.bottom || lrB.bottom < lrA.top) ? false : true
}

function parseLayers(layersCollection, layers, canvas, locked, lr) {
    for (var i = 0; i < layersCollection.length; i++) {
        if (isLocked(layersCollection[i].layerLocking)) {
            locked.push({ layerLocking: layersCollection[i].layerLocking, id: layersCollection[i].id })
            lr.applyLocking(layersCollection[i].id,
                {
                    "protectAll": false,
                    "protectArtboardAutonest": false,
                    "protectComposite": false,
                    "protectPosition": false,
                    "protectTransparency": false
                })
        }

        if (layersCollection[i].artboardRect) {
            canvas.push(layersCollection[i].artboardRect)
        }

        if (layersCollection[i].layerKind == 7) {
            parseLayers(layersCollection[i], layers, canvas, locked, lr)
        } else {
            layers.push(layersCollection[i])
        }
    }
}

function cleanupEmptyGroups(layersCollection, toDelete) {
    var isEmpty = true
    for (var i = 0; i < layersCollection.length; i++) {
        if (layersCollection[i].layerKind == 7 && !layersCollection[i].artboardRect) {
            if (layersCollection[i].length) {
                if (cleanupEmptyGroups(layersCollection[i], toDelete) && layersCollection.id) toDelete.push(layersCollection.id) else isEmpty = false
            } else {
                toDelete.push(layersCollection[i].id)
            }
        } else isEmpty = false
    }
    return isEmpty
}

function getLayersCollection(document, layer) {
    var indexFrom = document.getProperty('hasBackgroundLayer') ? 0 : 1,
        indexTo = document.getProperty('numberOfLayers');

    return layersCollection(indexFrom, indexTo)

    function layersCollection(from, to, parentItem, group) {
        parentItem = parentItem ? parentItem : [];

        for (var i = from; i <= to; i++) {
            if (layer.getProperty('layerSection', i, true).value == 'layerSectionEnd') {
                i = layersCollection(i + 1, to, [], parentItem)
                continue;
            }

            var p = {};
            p.id = layer.getProperty('layerID', i, true)
            p.layerKind = layer.getProperty('layerKind', i, true)
            p.layerLocking = layer.descToObject(layer.getProperty('layerLocking', i, true).value)
            p.bounds = [layer.descToObject(layer.getProperty('bounds', i, true).value)]

            if (layer.hasProperty('boundsNoMask')) p.bounds.push(layer.descToObject(layer.getProperty('boundsNoMask', i, true).value))
            if (layer.hasProperty('boundsNoEffects')) p.bounds.push(layer.descToObject(layer.getProperty('boundsNoEffects', i, true).value))

            p.bounds = p.bounds.sort(function (a, b) { return (a.right - a.left) + (a.bottom - a.top) > (b.right - b.left) + (b.bottom - b.top) ? 1 : 0 }).shift();

            if (p.layerKind == 7 && layer.getProperty('artboardEnabled', i, true)) {
                p.artboardRect = layer.descToObject(layer.getProperty('artboard', i, true).value.getObjectValue(stringIDToTypeID('artboardRect')))
            }

            if (layer.getProperty('layerSection', i, true).value == 'layerSectionStart') {
                for (o in p) { parentItem[o] = p[o] }
                group.push(parentItem);
                return i;
            } else {
                parentItem.push(p)
            }
        }
        return parentItem
    }
}

function isLocked(locking) {
    for (var a in locking) {
        if (locking[a]) return true
    }
    return false
}

function AM(target, order) {
    var s2t = stringIDToTypeID,
        t2s = typeIDToStringID;

    target = target ? s2t(target) : null;

    this.getProperty = function (property, id, idxMode) {
        property = s2t(property);
        (r = new ActionReference()).putProperty(s2t('property'), property);
        id != undefined ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id)) :
            r.putEnumerated(target, s2t('ordinal'), order ? s2t(order) : s2t('targetEnum'));
        return getDescValue(executeActionGet(r), property)
    }

    this.hasProperty = function (property, id, idxMode) {
        property = s2t(property);
        (r = new ActionReference()).putProperty(s2t('property'), property);
        id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
            : r.putEnumerated(target, s2t('ordinal'), order ? s2t(order) : s2t('targetEnum'));
        return executeActionGet(r).hasKey(property)
    }

    this.descToObject = function (d) {
        var o = {}
        for (var i = 0; i < d.count; i++) {
            var k = d.getKey(i)
            o[t2s(k)] = getDescValue(d, k)
        }
        return o
    }

    this.objectToDesc = function (o) {
        var d = new ActionDescriptor();
        for (var k in o) {
            var v = o[k];
            switch (typeof (v)) {
                case 'boolean': d.putBoolean(s2t(k), v); break;
                case 'string': d.putString(s2t(k), v); break;
                case 'number': d.putInteger(s2t(k), v); break;
            }
        }
        return d;
    }

    this.applyLocking = function (id, locking) {
        (r = new ActionReference()).putIdentifier(s2t('layer'), id);
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
        d.putObject(s2t('layerLocking'), s2t('layerLocking'), this.objectToDesc(locking));
        executeAction(s2t('applyLocking'), d, DialogModes.NO);
    }

    this.selectLayers = function (list) {
        var r = new ActionReference();
        do { r.putIdentifier(s2t('layer'), list.shift()) } while (list.length);
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
        executeAction(s2t('select'), d, DialogModes.NO);
    }

    this.deleteLayers = function (list) {
        var r = new ActionReference();
        do { r.putIdentifier(s2t('layer'), list.shift()) } while (list.length);
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
        executeAction(s2t('delete'), d, DialogModes.NO);
    }

    function getDescValue(d, p) {
        switch (d.getType(p)) {
            case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(p)), value: d.getObjectValue(p) };
            case DescValueType.LISTTYPE: return d.getList(p);
            case DescValueType.REFERENCETYPE: return d.getReference(p);
            case DescValueType.BOOLEANTYPE: return d.getBoolean(p);
            case DescValueType.STRINGTYPE: return d.getString(p);
            case DescValueType.INTEGERTYPE: return d.getInteger(p);
            case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(p);
            case DescValueType.DOUBLETYPE: return d.getDouble(p);
            case DescValueType.ALIASTYPE: return d.getPath(p);
            case DescValueType.CLASSTYPE: return d.getClass(p);
            case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(p));
            case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(p)), value: t2s(d.getEnumerationValue(p)) };
            default: break;
        };
    }
}

 

Brainiac
August 20, 2021

: )

Чё-то у меня удаляет всё подряд. Пустые группы, кстати, не удаляет.

Думаю, лучше поверять видимость слоя в пределах канваса, делая калькуляцию слоя в выделение и проверяя пересечение его (выделения) с выделением канваса.

Если слой сгуппирован со слоем вне канваса, то это проблема (без решнния пока, облом соображать...: )

 

 

Brainiac
August 22, 2021

Это норма Ⓒ 🙂

Я писал под последний шоп и совершенно забыл, что свойства width и height в дескрипторе bounds появились не так давно. Насколько понимаю, проблемы были из-за этого. Немного поправил код.
Группы удаляет в теле функции удаления слоев, т.е. если скрипту нечего удалять за канвасом, то он их не трогает. Наверное надо было сделать немного по-другому.

П.С. пока тестировал, обнаружил у себя документ с неудаляемой группой (т.е. ее реально никак не удалить - ни через интерфейс, ни через скрипт). Файл создан вручную (не скриптом), не поврежден. Вот кроп из него: неудаляемая группа 

JJMack
Community Expert
August 18, 2021

If you do not care to preserve your work in layers why are you saving layered files?  The script jazz-y posted will delete most Smart smart layer  that has content that is all off canvas.  His diagrams shows the exception case where the smart object layer's content is all off canvas however the layer bounds has some area over the documents canvas with no content.

 

Smart object layer have high overhead. However, they preserve the object quality.  Transforming and warping a smart object layer is none destructive. The object is not changed just the smart objects layer pixels are just a  new transformation of the object pixels every time you edit the Smart Object layer's object's transform.

JJMack
Participating Frequently
August 18, 2021

Part of my work requires using a massive master file that has text layers all over it, then cropping sections out to create a separate file. Cropping the master file itself takes time, but continuing to work in the new ones, while all the non-rasterized layers sit outside the canvas slows down my workflow. The closest solution I have found is doing 'reveal all' then selecting and deleting the smart objects to speed up photoshop, but doing so adds to the loading time since photoshop has to essentially render them again. Theoretically, I could rasterize the text layers, but it's not ideal. 

JJMack
Community Expert
August 18, 2021

You are confusing me. Smart object layer are not text layers. What are the smart object layer used for?  Why do your have smart object layers in you document that do not have content that is visible in the your outpur image files? Why do these smart layer even exists? If preserving the object is quality is not important,  Rasterizing all object layer would reduce the master file size and eliminate the smart object layer overhead and cropping the master document to canvas size would delete all content in layers outside the Document canvas.  Smart object layers are often use in mockup templates so the object content can be replace and be resize distorted like the smart object in the Mockup template. However some object content is visible in output image files.  Cropping Mockups the small parts of the mock does not seem to make sense to me. What are your massine master file normall used for. Why are they created like they have been.

JJMack
Brainiac
August 17, 2021

As i understand it, the end goal is to remove the layers, not select them.

Try this script* to delete all smart objects in a file outside the canvas **: 

 

#target photoshop

var doc = new AM('document'),
    lr = new AM('layer'),
    units = (new AM('application')).getProperty('rulerUnits').value,
    len = doc.getProperty('numberOfLayers'),
    smartObjects = [];

for (var i = 1; i <= len; i++) {
    if (lr.getProperty('layerSection', i, true).value == 'layerSectionEnd') continue;
    if (lr.getProperty('layerKind', i, true) == 5) {
        smartObjects.push({
            id: id = lr.getProperty('layerID', i, true),
            bounds: lr.descToObject(lr.getProperty('bounds', i, true).value)
        })
    }
}

if (smartObjects.length) {
    doc.setRulerUnits('rulerPixels')
    docBounds = { top: 0, left: 0, right: doc.getProperty('width'), bottom: doc.getProperty('height'), }
    doc.setRulerUnits(units)

    var toDelete = []
    do { if (!intersect((so = smartObjects.shift()).bounds, docBounds)) toDelete.push(so.id) } while (smartObjects.length)

    if (toDelete.length) doc.deleteLayers(toDelete) 
}

function intersect(lrA, lrB) {
    return (lrB.right < lrA.left || lrB.left > lrA.right) || (lrB.top > lrA.bottom || lrB.bottom < lrA.top) ? false : true
}

function AM(target, order) {
    var s2t = stringIDToTypeID,
        t2s = typeIDToStringID;

    target = target ? s2t(target) : null;

    this.getProperty = function (property, id, idxMode) {
        property = s2t(property);
        (r = new ActionReference()).putProperty(s2t('property'), property);
        id != undefined ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id)) :
            r.putEnumerated(target, s2t('ordinal'), order ? s2t(order) : s2t('targetEnum'));
        return getDescValue(executeActionGet(r), property)
    }

    this.descToObject = function (d) {
        var o = {}
        for (var i = 0; i < d.count; i++) {
            var k = d.getKey(i)
            o[t2s(k)] = getDescValue(d, k)
        }
        return o
    }

    this.setRulerUnits = function (units) {
        (r = new ActionReference()).putProperty(s2t('property'), s2t('unitsPrefs'));
        r.putEnumerated(s2t('application'), s2t('ordinal'), s2t('targetEnum'));
        (d = new ActionDescriptor).putReference(s2t('null'), r);
        (d1 = new ActionDescriptor).putEnumerated(s2t('rulerUnits'), s2t('rulerUnits'), s2t(units));
        d.putObject(s2t('to'), s2t('unitsPrefs'), d1);
        executeAction(s2t('set'), d, DialogModes.NO);
    }

    this.deleteLayers = function (list) {
        var r = new ActionReference();
        do { r.putIdentifier(s2t('layer'), list.shift()) } while (list.length);
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
          executeAction(s2t('delete'), d, DialogModes.NO);
    }

    function getDescValue(d, p) {
        switch (d.getType(p)) {
            case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(p)), value: d.getObjectValue(p) };
            case DescValueType.LISTTYPE: return d.getList(p);
            case DescValueType.REFERENCETYPE: return d.getReference(p);
            case DescValueType.BOOLEANTYPE: return d.getBoolean(p);
            case DescValueType.STRINGTYPE: return d.getString(p);
            case DescValueType.INTEGERTYPE: return d.getInteger(p);
            case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(p);
            case DescValueType.DOUBLETYPE: return d.getDouble(p);
            case DescValueType.ALIASTYPE: return d.getPath(p);
            case DescValueType.CLASSTYPE: return d.getClass(p);
            case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(p));
            case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(p)), value: t2s(d.getEnumerationValue(p)) };
            default: break;
        };
    }
}

 

 

 

* save this code to a text file, change its extension to .jsx, place it in the presets folder (Adobe Photoshop\Presets\Scripts\). Restart Photoshop, the script will appear in the File -> Scripts menu, after that manual launch, hotkey launch, recording to the action will be available. 

** the script does not use the actual borders of the smart object, but its bounding box. In most cases this will not be a problem, but if you place smart objects close to the corners of the canvas, then the script can ignore such layers. 

Participating Frequently
August 18, 2021

Thanks so much for this! I got an error when trying to run it, though. Thoughts?

 

Brainiac
August 18, 2021

Yes, there was an error in the code (the code is written for a document that has a background layer ), I fixed it, but perhaps you download it before this moment. Try to copy it again and run it.

Are you using artboards?