Skip to main content
Participating Frequently
November 15, 2021
Answered

JSX script: using Action Manager to several layers at once by IDs

  • November 15, 2021
  • 2 replies
  • 1902 views

Hi guys,

I have a custom logic-heavy export script that needs to hide layers before it begins execution. And I need to use logic to figure out which layers I need to hide, so using commands like Select All Layers and then Hide Selected Layers won't cut it here.

I have a working version of the script that uses recursive DOM traversal and hides the necessary layers that way, but the performance is ridiculously slow. Some of the files I need this to run on are 1000+ layers, and it can literally take a minute to simply loop through all layers.

By using Script Listener, I have found some code that does pretty much exactly what I need:

 

var idhide = stringIDToTypeID( "hide" );
var desc877 = new ActionDescriptor();
var idnull = stringIDToTypeID( "null" );
var list111 = new ActionList();
var ref384 = new ActionReference();
var idlayer = stringIDToTypeID( "layer" );
ref384.putName( idlayer, "Layer 1" );
ref384.putName( idlayer, "Layer 2" );
ref384.putName( idlayer, "Layer 3" );
ref384.putName( idlayer, "Layer 4" );
list111.putReference( ref384 );
desc877.putList( idnull, list111 );
executeAction( idhide, desc877, DialogModes.NO );

 

I can basically use this to take a list of layer names and hide them all instantly. Marvellous! Except layer names are not unique, so this falls apart completely if any two layers share the same name, and it's a complete deal breaker for my case.

 

I need a version of the same code that uses layer IDs instead of names, but I have no idea how to construct it or where to look for reference (I presume it doesn't exist). I have found some functions in Photoshop that generate code that looks very similar to what I'm looking for, i.e. selecting a bunch of layers creates this snippet:

 

...

var idlayerID = stringIDToTypeID( "layerID" );
var list110 = new ActionList();
list110.putInteger( 238 );
list110.putInteger( 239 );
list110.putInteger( 242 );
list110.putInteger( 250 );
desc872.putList( idlayerID, list110 );

...

 

But I haven't been able to come up with a way to use this mode of passing layers to the "hide" command. And even in the "select" command it doesn't seem to work despite appearing like this in the Script Listener's log.

 

Any ideas?

This topic has been closed for replies.
Correct answer jazz-y

 

visiblityByIDList(getAllLayersIDs(), 'hide');

// IDList - array of layer IDs
// mode - 'hide' or 'show'
function visiblityByIDList(IDList, mode) {
    var s2t = stringIDToTypeID,
        r = new ActionReference();
    do { r.putIdentifier(s2t('layer'), IDList.shift()) } while (IDList.length)
    var d = new ActionDescriptor();
    d.putReference(s2t('target'), r);
    executeAction(s2t(mode), d, DialogModes.NO);
}

// just for example
function getAllLayersIDs() {
    s2t = stringIDToTypeID;
    (ref = new ActionReference()).putProperty(s2t('property'), p = s2t('numberOfLayers'));
    ref.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
    var len = executeActionGet(ref).getInteger(p),
        lrs = [];
    for (var i = 1; i <= len; i++) {
        (r = new ActionReference()).putProperty(s2t('property'), p = s2t('layerID'));
        r.putIndex(s2t('layer'), i);
        lrs.push(executeActionGet(r).getInteger(p))
    }
    return lrs
}

 

2 replies

jazz-yCorrect answer
Legend
November 15, 2021

 

visiblityByIDList(getAllLayersIDs(), 'hide');

// IDList - array of layer IDs
// mode - 'hide' or 'show'
function visiblityByIDList(IDList, mode) {
    var s2t = stringIDToTypeID,
        r = new ActionReference();
    do { r.putIdentifier(s2t('layer'), IDList.shift()) } while (IDList.length)
    var d = new ActionDescriptor();
    d.putReference(s2t('target'), r);
    executeAction(s2t(mode), d, DialogModes.NO);
}

// just for example
function getAllLayersIDs() {
    s2t = stringIDToTypeID;
    (ref = new ActionReference()).putProperty(s2t('property'), p = s2t('numberOfLayers'));
    ref.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
    var len = executeActionGet(ref).getInteger(p),
        lrs = [];
    for (var i = 1; i <= len; i++) {
        (r = new ActionReference()).putProperty(s2t('property'), p = s2t('layerID'));
        r.putIndex(s2t('layer'), i);
        lrs.push(executeActionGet(r).getInteger(p))
    }
    return lrs
}

 

danielsian.com
Inspiring
November 15, 2021

I'm not sure what the criteria are for determining which layer should be visible or not.
As I understand from your last snippet, the criteria are based on layer selection.
This function will handle only selected layers or all layers if nothing is selected.

 

function setVisibility(visible){
	// visible: 'show' or 'hide'
	selection = false;
	desc = new ActionDescriptor();
	desc2 = new ActionDescriptor();
	ref = new ActionReference();
	ref2 = new ActionReference();
	ref3 = new ActionReference();
	ref.putProperty( stringIDToTypeID('property'), stringIDToTypeID('targetLayersIDs') );
	ref.putEnumerated( stringIDToTypeID('document'), stringIDToTypeID('ordinal'), stringIDToTypeID('targetEnum') );
	ref2.putIndex(stringIDToTypeID("layer"), 1); // ignores background
	desc.putReference(stringIDToTypeID("null"), ref2);
	if ( !executeActionGet(ref).hasKey(stringIDToTypeID('targetLayersIDs')) ) {
		executeAction(stringIDToTypeID("selectAllLayers"), desc, DialogModes.NO); // select all layers
		selection = true
	}
	list = executeActionGet(ref).getList( stringIDToTypeID('targetLayersIDs') );
	for ( var i = 0; i < list.count; i++ ) {
		ref3.putIdentifier(stringIDToTypeID('layer'), list.getReference( i ).getIdentifier());
		desc2.putReference(stringIDToTypeID('target'), ref3);
		executeAction(stringIDToTypeID(visible), desc2, DialogModes.NO);
	}
	if (selection) executeAction(stringIDToTypeID("selectNoLayers"), desc, DialogModes.NO); // select no layers
}

 

Legend
November 15, 2021

This is just an example of using a function. The snippet collects the IDs of all layers in the document, regardless of the selection.

 

By the way, just one change will speed up your code 5-6 times 🙂

change:

 

for ( var i = 0; i < list.count; i++ ) {
		ref3.putIdentifier(stringIDToTypeID('layer'), list.getReference( i ).getIdentifier());
		desc2.putReference(stringIDToTypeID('target'), ref3);
		executeAction(stringIDToTypeID(visible), desc2, DialogModes.NO);
	}

 

to:

 

for ( var i = 0; i < list.count; i++ ) {
		ref3.putIdentifier(stringIDToTypeID('layer'), list.getReference( i ).getIdentifier());
		desc2.putReference(stringIDToTypeID('target'), ref3);
	}
		executeAction(stringIDToTypeID(visible), desc2, DialogModes.NO);

 

(this is especially noticeable on documents with a large number of layers)

 

Also, if we are talking about selection of layers on the palette, there is no point in wasting time getting their ID, just execute "hide" or "show" for the active layer (and the command will automatically apply to all selected layers)

 

ref3 = new ActionReference();
desc2 = new ActionDescriptor();
ref3.putEnumerated(stringIDToTypeID('layer'),  stringIDToTypeID('ordinal'), stringIDToTypeID('targetEnum'));
desc2.putReference(stringIDToTypeID('target'), ref3);
executeAction(stringIDToTypeID('hide'), desc2, DialogModes.NO);

 

 

JJMack
Community Expert
Community Expert
November 15, 2021

How do you know which layer ID should be visible and which layer ID should not be visible. I can see where when there may duplicate duplicate layer names that layer name can not be used for controlling visibility. I would think Relative stack location may use for targeting layers that visibility should be changed.  Layer id are unique in a document however the layer position in the  will change with the additions and deletion and moving of layers in the stack. How do you know what  content the layer with ID x has or where in the stack the layer with ID x is. What logic do you use to determine that layer with ID x visibility should be on or off. How do yor manage a 1000+ layer stack. Perhapse you should look into using layer comps. Layer comps

JJMack
Participating Frequently
November 15, 2021

The exporter I'm writing is used for preparing game assets. The logic emerges from the way assets in the file are organized. It's usually a complex scene with a lot of nested groups for objects and scene depths and categories and characters. Some objects have multiple timeline animations that need to be exported at different framerates, some exist in several variants dictated by combinations of layers within a group, some have arbitrary boundaries associated with them, etc. I'm using layer names to describe all this behavior and then I parse all of it inside the script, and by that point I have a complete model of everything I need to know about the document, including the right layer IDs. I'm just lacking a quick way of feeding it into Photoshop's API.

The 1000+ layers is not really a problem for artistic workflow because there's rhyme and reason to it and everything is organized in numerous groups (i.e. I can have "scene/level 5/background/objects/furniture/table" + 30 more furniture items in the same file, it adds up quickly). This is really just a domain specific thing I guess 🙂 Layer comps can't deal with this. I've actually implemented my own version of this that operates at group level — and that's another thing that I would love to teach how to hide layers quickly, but no luck so far. It all works beautifully, it's just horribly sluggish.