Skip to main content
Participating Frequently
February 10, 2025
Answered

Quick way to export multiple slices per layer comp?

  • February 10, 2025
  • 4 replies
  • 729 views

In my workflow I make heavy use of both layer comps and slices. A typical file for me is a large sheet of assets with one asset per slice, then to handle colour variants I use layer comps. Think a big texture sheet with a bunch of furniture, trinkets, weapons, whatever on it. Then the layer comps changes the colours of these items.

 

When the time comes to export these assets, my process looks like this:

 

  1. File > Export > Save for Web
  2. Once exported, I use another bit of software to batch append the name of the layer comp to each slice. EG, if the slice was named 'Barrel_01' and the layer comp was 'Red', I would rename the exported file to 'Barrel_01_Red'.
  3. Move to the next layer comp.
  4. Repeat until everything is exported.

 

As you can imagine, that is terribly inefficient for large asset sheets. I have noticed that the 'File > Export > Layer Comps to Files' option does export the slices for every layer comp, but it doesn't take into account the naming convention, meaning the export just overwrites the files from the previous comp with the next (Unless I am missing something built into PS to avoid that?).

 

Whats the solution here? Scripting? Batch setup? Some built-in feature I am missing (fingers crossed)?

Correct answer c.pfaffenbichler
// slices and layer comp combinations;
// based on code by michael l hale;
// 2025, use it at your own risk;
if (app.documents.length > 0) {
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var myDocument = app.activeDocument;
var docName = myDocument.name;
try {
var basename = docName.match(/(.*)\.[^\.]+$/)[1];
var origDocPath = myDocument.fullName;
var docPath = myDocument.path;
}
catch (e) {
var basename = myDocument.name;
var docPath = "~/Desktop"
};
// get properties;
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
var docDesc = executeActionGet(ref);
var theComps = docDesc.getList(stringIDToTypeID("compsList"));
var theSlices = docDesc.getObjectValue(stringIDToTypeID("slices")).getList(stringIDToTypeID("slices"));
var theArray = new Array;
for (var m = 0; m < theSlices.count; m++) {
var thisOne = theSlices.getObjectValue(m);
if (thisOne.hasKey(stringIDToTypeID("name")) == true) {theArray.push([thisOne.getString(stringIDToTypeID("name")), thisOne.getObjectValue(stringIDToTypeID("bounds")).getInteger(stringIDToTypeID("left")), thisOne.getObjectValue(stringIDToTypeID("bounds")).getInteger(stringIDToTypeID("top")), thisOne.getObjectValue(stringIDToTypeID("bounds")).getInteger(stringIDToTypeID("right")), thisOne.getObjectValue(stringIDToTypeID("bounds")).getInteger(stringIDToTypeID("bottom"))])}
};
var theCompsArray = new Array;
for (var m = 0; m < theComps.count; m++) {
var thisOne = theComps.getObjectValue(m);
theCompsArray.push(thisOne.getString(stringIDToTypeID("title")))
};
// process the slices and arrays;
if (theArray.length != 0 && theCompsArray.length != 0) {
var theCopy = activeDocument.duplicate("theCopy");
var theState = theCopy.activeHistoryState;
for (var n = 0; n < theArray.length; n++) {
    theCopy.crop([theArray[n][1], theArray[n][2], theArray[n][3], theArray[n][4]]);
    var theState2 = theCopy.activeHistoryState;
    for (var o = 0; o < theCompsArray.length; o++) {
        applyLayerComp (o+1);
        savePNG (theCopy, docPath, basename, theArray[n][0]+"_"+theCompsArray[o])
        theCopy.activeHistoryState = theState2;
    };
    theCopy.activeHistoryState = theState;
};
theCopy.close(SaveOptions.DONOTSAVECHANGES);
};
// reset;
app.preferences.rulerUnits = originalRulerUnits;
};
////////////////////////////////////
////// based on code by michael l hale //////
function checkDesc2 (theDesc, theAlert) {
var c = theDesc.count;
var str = '';
for(var i=0;i<c;i++){ //enumerate descriptor's keys
str = str + 'Key '+i+' = '+typeIDToStringID(theDesc.getKey(i))+': '+theDesc.getType(theDesc.getKey(i))+'\n'+getValues (theDesc, i)+'\n';
};
if (theAlert == true) {alert("desc\n\n"+str);
return};
return str
};
////// check //////
function getValues (theDesc, theNumber) {
switch (theDesc.getType(theDesc.getKey(theNumber))) {
case DescValueType.ALIASTYPE:
return theDesc.getPath(theDesc.getKey(theNumber));
break;
case DescValueType.BOOLEANTYPE:
return theDesc.getBoolean(theDesc.getKey(theNumber));
break;
case DescValueType.CLASSTYPE:
return theDesc.getClass(theDesc.getKey(theNumber));
break;
case DescValueType.DOUBLETYPE:
return theDesc.getDouble(theDesc.getKey(theNumber));
break;
case DescValueType.ENUMERATEDTYPE:
return (typeIDToStringID(theDesc.getEnumerationValue(theDesc.getKey(theNumber)))+"_"+typeIDToStringID(theDesc.getEnumerationType(theDesc.getKey(theNumber))));
break;
case DescValueType.INTEGERTYPE:
return theDesc.getInteger(theDesc.getKey(theNumber));
break;
case DescValueType.LISTTYPE:
return theDesc.getList(theDesc.getKey(theNumber));
break;
case DescValueType.OBJECTTYPE:
return (theDesc.getObjectValue(theDesc.getKey(theNumber))+"_"+typeIDToStringID(theDesc.getObjectType(theDesc.getKey(theNumber))));
break;
case DescValueType.RAWTYPE:
return theDesc.getReference(theDesc.getData(theNumber));
break;
case DescValueType.REFERENCETYPE:
return theDesc.getReference(theDesc.getKey(theNumber));
break;
case DescValueType.STRINGTYPE:
return theDesc.getString(theDesc.getKey(theNumber));
break;
case DescValueType.UNITDOUBLE:
return (theDesc.getUnitDoubleValue(theDesc.getKey(theNumber))+"_"+typeIDToStringID(theDesc.getUnitDoubleType(theDesc.getKey(theNumber))));
break;
default: 
break;
};
};
////// apply layer comp //////
function applyLayerComp (theIndex) {
// =======================================================
var desc6 = new ActionDescriptor();
var ref3 = new ActionReference();
ref3.putIndex( stringIDToTypeID( "compsClass" ), theIndex );
//ref3.putName( stringIDToTypeID( "compsClass" ), theName );
desc6.putReference( stringIDToTypeID( "null" ), ref3 );
executeAction( stringIDToTypeID( "applyComp" ), desc6, DialogModes.NO );
};
////// function to png //////
function savePNG (myDocument, docPath, basename, theSuffix) {
var desc10 = new ActionDescriptor();
var desc11 = new ActionDescriptor();
desc11.putEnumerated( stringIDToTypeID( "method" ), stringIDToTypeID( "PNGMethod" ), stringIDToTypeID( "quick" ) );
desc11.putEnumerated( stringIDToTypeID( "PNGInterlaceType" ), stringIDToTypeID( "PNGInterlaceType" ), stringIDToTypeID( "PNGInterlaceNone" ) );
desc11.putEnumerated( stringIDToTypeID( "PNGFilter" ), stringIDToTypeID( "PNGFilter" ), stringIDToTypeID( "PNGFilterAdaptive" ) );
desc11.putInteger( stringIDToTypeID( "compression" ), 6 );
desc11.putEnumerated( stringIDToTypeID( "embedIccProfileLastState" ), stringIDToTypeID( "embedOff" ), stringIDToTypeID( "embedOff" ) );
var idPNGFormat = stringIDToTypeID( "PNGFormat" );
desc10.putObject( stringIDToTypeID( "as" ), idPNGFormat, desc11 );
desc10.putPath( stringIDToTypeID( "in" ), new File( docPath+"/"+basename+"_"+theSuffix+".png" ) );
desc10.putBoolean( stringIDToTypeID( "copy" ), true );
desc10.putBoolean( stringIDToTypeID( "lowerCase" ), true );
desc10.putBoolean( stringIDToTypeID( "embedProfiles" ), true );
desc10.putEnumerated( stringIDToTypeID( "saveStage" ), stringIDToTypeID( "saveStageType" ), stringIDToTypeID( "saveSucceeded" ) );
executeAction( stringIDToTypeID( "save" ), desc10, DialogModes.NO );
};

4 replies

Stephen Marsh
Adobe Expert
February 15, 2025

@Jamie Squared 

 

@c.pfaffenbichler has posted a script, please try it out and provide feedback, marking the post as a correct answer if it helped.

c.pfaffenbichler
c.pfaffenbichlerCorrect answer
Adobe Expert
February 15, 2025
// slices and layer comp combinations;
// based on code by michael l hale;
// 2025, use it at your own risk;
if (app.documents.length > 0) {
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var myDocument = app.activeDocument;
var docName = myDocument.name;
try {
var basename = docName.match(/(.*)\.[^\.]+$/)[1];
var origDocPath = myDocument.fullName;
var docPath = myDocument.path;
}
catch (e) {
var basename = myDocument.name;
var docPath = "~/Desktop"
};
// get properties;
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") ); 
var docDesc = executeActionGet(ref);
var theComps = docDesc.getList(stringIDToTypeID("compsList"));
var theSlices = docDesc.getObjectValue(stringIDToTypeID("slices")).getList(stringIDToTypeID("slices"));
var theArray = new Array;
for (var m = 0; m < theSlices.count; m++) {
var thisOne = theSlices.getObjectValue(m);
if (thisOne.hasKey(stringIDToTypeID("name")) == true) {theArray.push([thisOne.getString(stringIDToTypeID("name")), thisOne.getObjectValue(stringIDToTypeID("bounds")).getInteger(stringIDToTypeID("left")), thisOne.getObjectValue(stringIDToTypeID("bounds")).getInteger(stringIDToTypeID("top")), thisOne.getObjectValue(stringIDToTypeID("bounds")).getInteger(stringIDToTypeID("right")), thisOne.getObjectValue(stringIDToTypeID("bounds")).getInteger(stringIDToTypeID("bottom"))])}
};
var theCompsArray = new Array;
for (var m = 0; m < theComps.count; m++) {
var thisOne = theComps.getObjectValue(m);
theCompsArray.push(thisOne.getString(stringIDToTypeID("title")))
};
// process the slices and arrays;
if (theArray.length != 0 && theCompsArray.length != 0) {
var theCopy = activeDocument.duplicate("theCopy");
var theState = theCopy.activeHistoryState;
for (var n = 0; n < theArray.length; n++) {
    theCopy.crop([theArray[n][1], theArray[n][2], theArray[n][3], theArray[n][4]]);
    var theState2 = theCopy.activeHistoryState;
    for (var o = 0; o < theCompsArray.length; o++) {
        applyLayerComp (o+1);
        savePNG (theCopy, docPath, basename, theArray[n][0]+"_"+theCompsArray[o])
        theCopy.activeHistoryState = theState2;
    };
    theCopy.activeHistoryState = theState;
};
theCopy.close(SaveOptions.DONOTSAVECHANGES);
};
// reset;
app.preferences.rulerUnits = originalRulerUnits;
};
////////////////////////////////////
////// based on code by michael l hale //////
function checkDesc2 (theDesc, theAlert) {
var c = theDesc.count;
var str = '';
for(var i=0;i<c;i++){ //enumerate descriptor's keys
str = str + 'Key '+i+' = '+typeIDToStringID(theDesc.getKey(i))+': '+theDesc.getType(theDesc.getKey(i))+'\n'+getValues (theDesc, i)+'\n';
};
if (theAlert == true) {alert("desc\n\n"+str);
return};
return str
};
////// check //////
function getValues (theDesc, theNumber) {
switch (theDesc.getType(theDesc.getKey(theNumber))) {
case DescValueType.ALIASTYPE:
return theDesc.getPath(theDesc.getKey(theNumber));
break;
case DescValueType.BOOLEANTYPE:
return theDesc.getBoolean(theDesc.getKey(theNumber));
break;
case DescValueType.CLASSTYPE:
return theDesc.getClass(theDesc.getKey(theNumber));
break;
case DescValueType.DOUBLETYPE:
return theDesc.getDouble(theDesc.getKey(theNumber));
break;
case DescValueType.ENUMERATEDTYPE:
return (typeIDToStringID(theDesc.getEnumerationValue(theDesc.getKey(theNumber)))+"_"+typeIDToStringID(theDesc.getEnumerationType(theDesc.getKey(theNumber))));
break;
case DescValueType.INTEGERTYPE:
return theDesc.getInteger(theDesc.getKey(theNumber));
break;
case DescValueType.LISTTYPE:
return theDesc.getList(theDesc.getKey(theNumber));
break;
case DescValueType.OBJECTTYPE:
return (theDesc.getObjectValue(theDesc.getKey(theNumber))+"_"+typeIDToStringID(theDesc.getObjectType(theDesc.getKey(theNumber))));
break;
case DescValueType.RAWTYPE:
return theDesc.getReference(theDesc.getData(theNumber));
break;
case DescValueType.REFERENCETYPE:
return theDesc.getReference(theDesc.getKey(theNumber));
break;
case DescValueType.STRINGTYPE:
return theDesc.getString(theDesc.getKey(theNumber));
break;
case DescValueType.UNITDOUBLE:
return (theDesc.getUnitDoubleValue(theDesc.getKey(theNumber))+"_"+typeIDToStringID(theDesc.getUnitDoubleType(theDesc.getKey(theNumber))));
break;
default: 
break;
};
};
////// apply layer comp //////
function applyLayerComp (theIndex) {
// =======================================================
var desc6 = new ActionDescriptor();
var ref3 = new ActionReference();
ref3.putIndex( stringIDToTypeID( "compsClass" ), theIndex );
//ref3.putName( stringIDToTypeID( "compsClass" ), theName );
desc6.putReference( stringIDToTypeID( "null" ), ref3 );
executeAction( stringIDToTypeID( "applyComp" ), desc6, DialogModes.NO );
};
////// function to png //////
function savePNG (myDocument, docPath, basename, theSuffix) {
var desc10 = new ActionDescriptor();
var desc11 = new ActionDescriptor();
desc11.putEnumerated( stringIDToTypeID( "method" ), stringIDToTypeID( "PNGMethod" ), stringIDToTypeID( "quick" ) );
desc11.putEnumerated( stringIDToTypeID( "PNGInterlaceType" ), stringIDToTypeID( "PNGInterlaceType" ), stringIDToTypeID( "PNGInterlaceNone" ) );
desc11.putEnumerated( stringIDToTypeID( "PNGFilter" ), stringIDToTypeID( "PNGFilter" ), stringIDToTypeID( "PNGFilterAdaptive" ) );
desc11.putInteger( stringIDToTypeID( "compression" ), 6 );
desc11.putEnumerated( stringIDToTypeID( "embedIccProfileLastState" ), stringIDToTypeID( "embedOff" ), stringIDToTypeID( "embedOff" ) );
var idPNGFormat = stringIDToTypeID( "PNGFormat" );
desc10.putObject( stringIDToTypeID( "as" ), idPNGFormat, desc11 );
desc10.putPath( stringIDToTypeID( "in" ), new File( docPath+"/"+basename+"_"+theSuffix+".png" ) );
desc10.putBoolean( stringIDToTypeID( "copy" ), true );
desc10.putBoolean( stringIDToTypeID( "lowerCase" ), true );
desc10.putBoolean( stringIDToTypeID( "embedProfiles" ), true );
desc10.putEnumerated( stringIDToTypeID( "saveStage" ), stringIDToTypeID( "saveStageType" ), stringIDToTypeID( "saveSucceeded" ) );
executeAction( stringIDToTypeID( "save" ), desc10, DialogModes.NO );
};

Participating Frequently
February 17, 2025

Works like a charm! Thanks a bunch, shocked at how fast it exports too.

Stephen Marsh
Adobe Expert
February 11, 2025
quote

I have noticed that the 'File > Export > Layer Comps to Files' option does export the slices for every layer comp, but it doesn't take into account the naming convention, meaning the export just overwrites the files from the previous comp with the next (Unless I am missing something built into PS to avoid that?).

 

By @Jamie Squared


Layer Comps to Files is a script, so perhaps something can be done with the naming there, adding the active comp name etc. These old Adobe scripts are large and complex, so sometimes even minor edits can be difficult.

 

Otherwise, a custom export script would probably be the best option.

 

EDIT: I found an "extended" version of the Layer Comps to Files script in my archive. I have attached it by renaming it from a .jsx to .txt file. After downloading you will need to rename it back to .jsx, perhaps the extra features might work for you.

c.pfaffenbichler
Adobe Expert
February 10, 2025

Scripting should provide one option. 

 

Could you provide a sample file and the resulting exported images? (Feel free to Blur or Mosaic or … the Layers’ content to avoid confidentiality issues.) 

Participating Frequently
February 10, 2025

Sure! 

 

Attached is the .psd for one of them and the first two rounds of exports. The client is myself so thankfully no need to obscure anything.

 

File is too large for Adobe (even when zipped) so it's hosted on Google Drive here.

c.pfaffenbichler
Adobe Expert
February 11, 2025

Thanks. 

Save for Web doesn’t work out for me; saving regular pngs would be an alternative but one would need to determine which slices carry meaning. 

 

If no one else chimes in I hope to be able to look into this further on the weekend.