Skip to content

Instantly share code, notes, and snippets.

@keiranlovett
Forked from GetUpKidAK/ExportTextures.jsx
Last active August 29, 2015 14:27
Show Gist options
  • Save keiranlovett/f39b9f70f24930d93609 to your computer and use it in GitHub Desktop.
Save keiranlovett/f39b9f70f24930d93609 to your computer and use it in GitHub Desktop.
Photoshop script to export textures from layered PSD
// Enables exporting of several PBR maps from a single PSD with a few clicks:
// 1. Select an export folder (default to PSD path on Windows)
// 2. Choose which PSD layer group corresponds to which map (split into separate RGB/Alpha channels)
// 3. Change the file export options (if required)
// 4. Hit export.
#target photoshop
app.bringToFront();
// DEFAULT EXPORT OPTIONS
var options = {
autoSave: true,
lowercaseFilename: true,
removeFilenameSpaces: true,
closeDocsOnSave:true,
exportedFileExtension: ".tga" // Not fully support, don't change!
};
// MAP CONSTRUCTOR (includes defaults)
var Map = function(defaultLayerName, defaultAlphaLayerName, filePostfix, alphaState)
{
this.defaultLayerName = defaultLayerName;
this.defaultAlphaLayerName = defaultAlphaLayerName;
this.filePostfix = filePostfix;
this.exportMap = false;
this.exportAlpha = false;
this.rgbLayerIndex = 0;
this.alphaLayerIndex = 0;
this.alphaState = alphaState;
this.setLayerIndexes = function(rgb, a)
{
this.rgbLayerIndex = rgb;
this.alphaLayerIndex = a;
this.exportMap = rgb > 0 ? true : false;
this.exportAlpha = a > 0 ? true : false;
};
this.selectedToExport = function()
{
return this.exportMap || this.exportAlpha;
}
this.readyToExport = function()
{
if (!this.exportMap && this.exportAlpha)
{
errorsLog += "- The " + this.defaultLayerName + " map won't be exported. You can't export an Alpha channel without an RGB channel.\n\n";
return false;
}
else if (this.exportMap && (!this.exportAlpha && this.alphaState == alpha.REQUIRED))
{
errorsLog += "- The " + this.defaultLayerName + " map won't be exported. The required Alpha channel wasn't specified.\n\n";
return false;
}
else if (this.exportMap && (this.exportAlpha && this.alphaState == alpha.DISABLED))
{
errorsLog += "- The " + this.defaultLayerName + " map won't be exported. The Alpha channel can't be exported on this map.\n\n";
return false;
}
return true;
}
}
var alpha = { AVAILABLE: 0, REQUIRED: 1, DISABLED: 2 }
//TODO: Loop to create form sections for each variable created here
var maps = {};
maps.albedo = new Map("albedo", "", "_albedo", alpha.AVAILABLE);
maps.normal = new Map("normal", "", "_normal", alpha.AVAILABLE);
maps.metallic = new Map("metallic", "smoothness", "_metallic", alpha.REQUIRED);
// Document caches
var doc; // Active document
var docFilename; // Document filename
var docPath; // Document path
// Other variables
var exportedFilename; // Filename for exported file
var exportedFilePath; // Path for exported file
var errorsLog; // Error log
// Let the fun begin...
function showDialog()
{
// Create the window
var win = new Window ("dialog", "Export PBR textures");
var mainPanel = win.add("panel", undefined, "Export Textures");
// Add a group for the export path
var folderGroup = mainPanel.add("group");
folderGroup.add("statictext", undefined, "Export folder: ");
var exportPath = folderGroup.add("edittext", undefined, docPath.fsName);
exportPath.size = [400, ""];
var selectFolderBtn = folderGroup.add ("iconbutton", undefined, "DestinationFolderIcon");
//TODO: Option to enter base filename
// Add a panel for the albedo map labels and drop-downs
var albedoGroup = mainPanel.add("panel", undefined, "Albedo map:");
albedoGroup.orientation = "row";
albedoGroup.add("statictext", undefined, "RGB channel: ");
var albedoRGB = albedoGroup.add("listbox");
albedoRGB.size = "width: 120, height: 120";
populateList(albedoRGB, maps.albedo.defaultLayerName);
albedoGroup.add("statictext", undefined, "Alpha channel: ");
var albedoAlpha = albedoGroup.add("listbox");
albedoAlpha.size = "width: 120, height: 120";
populateList(albedoAlpha, maps.albedo.defaultAlphaLayerName);
// Add a panel for the normal map labels and drop-downs
var normalGroup = mainPanel.add("panel", undefined, "Normal map:");
normalGroup.orientation = "row";
normalGroup.add("statictext", undefined, "RGB channel: ");
var normalRGB = normalGroup.add("listbox");
normalRGB.size = "width: 120, height: 120";
populateList(normalRGB, maps.normal.defaultLayerName);
normalGroup.add("statictext", undefined, "Alpha channel: ");
var normalAlpha = normalGroup.add("listbox");
normalAlpha.size = "width: 120, height: 120";
populateList(normalAlpha, maps.normal.defaultAlphaLayerName);
// Add a group for the metallic map labels and drop-downs
var metallicGroup = mainPanel.add("panel", undefined, "Metallic map:");
metallicGroup.orientation = "row";
metallicGroup.add("statictext", undefined, "RGB channel: ");
var metallicRGB = metallicGroup.add("listbox");
metallicRGB.size = "width: 120, height: 120";
populateList(metallicRGB, maps.metallic.defaultLayerName);
metallicGroup.add("statictext", undefined, "Alpha channel: ");
var metallicAlpha = metallicGroup.add("listbox");
metallicAlpha.size = "width: 120, height: 120";
populateList(metallicAlpha, maps.metallic.defaultAlphaLayerName);
// Export options group
var optionsGroup = mainPanel.add("panel", undefined, "Export options");
var autoSaveCheckbox = optionsGroup.add("checkbox", undefined, "Auto-save files (Overwrites any existing files)");
autoSaveCheckbox.value = options.autoSave;
var lowercaseCheckbox = optionsGroup.add("checkbox", undefined, "Convert filename to lowercase ('ExportMap' to 'exportmap')");
lowercaseCheckbox.value = options.lowercaseFilename;
var removeSpacesCheckbox = optionsGroup.add("checkbox", undefined, "Convert filename spaces to underscores ('export map' to 'export_map')");
removeSpacesCheckbox.value = options.removeFilenameSpaces;
var closeOnSaveCheckbox = optionsGroup.add("checkbox", undefined, "Close exported documents when auto-saved");
closeOnSaveCheckbox.value = options.closeDocsOnSave;
// Add buttons group
var buttonGroup = mainPanel.add("group");
var exportBtn = buttonGroup.add("button", undefined, "Export maps", {name: "ok"});
var cancelBtn = buttonGroup.add("button", undefined, "Cancel");
// CALLBACKS -- SELECT FOLDER
selectFolderBtn.onClick = function ()
{
var newPath = docPath.selectDlg("Select a folder to export to: ");
if (newPath != null)
{
exportPath.text = newPath.fsName;
}
}
// CALLBACKS -- EXPORT
exportBtn.onClick = function ()
{
options.autoSave = autoSaveCheckbox.value;
options.lowercaseFilename = lowercaseCheckbox.value;
options.removeFilenameSpaces = removeSpacesCheckbox.value;
options.closeDocsOnSave = closeOnSaveCheckbox.value;
exportedFilePath = exportPath.text;
maps.albedo.setLayerIndexes(albedoRGB.selection, albedoAlpha.selection);
maps.normal.setLayerIndexes(normalRGB.selection, normalAlpha.selection);
maps.metallic.setLayerIndexes(metallicRGB.selection, metallicAlpha.selection);
if (validateForm()) win.close(1); // If validation passes
else { alert(errorsLog); }
errorsLog = "The following errors occurred: \n\n";
}
if (win.show() == 1)
{
errorsLog = "The following errors occurred: \n\n";
return 0;
}
else
{
return 1;
}
}
function populateList (list, defaultLayerName)
{
list.add("item", "None");
list.selection = 0;
for (var i = 0; i < doc.layerSets.length; i++)
{
var layerName = doc.layerSets[i].name;
list.add("item", layerName);
if (layerName.toLowerCase() == defaultLayerName)
{
list.selection = list.find(layerName);
}
}
}
function validateForm ()
{
if (exportMapSelectionsValid() && folderExists ()) return true;
}
function exportMapSelectionsValid ()
{
var mapsToExport = 0;
var errorsCount = 0;
for (var map in maps)
{
if (maps[map].selectedToExport())
{
if (!maps[map].readyToExport())
{
errorsCount++;
}
else
{
mapsToExport++;
}
}
}
if (mapsToExport == 0) { errorsLog += "- There are no maps selected for export.\n\n"; errorsCount++; }
if (errorsCount > 0) return false;
return true;
}
function folderExists ()
{
if (exportedFilePath.exists) return true;
else
{
var newPath = new Folder (exportedFilePath);
if (newPath.create()) return true;
else
{
errorsLog += "- The export folder doesn't exist and couldn't be created. Please check you've entered a valid path.\n\n";
return false;
}
}
}
function getBaseFilename()
{
if (options.lowercaseFilename) exportedFilename = exportedFilename.toLowerCase();
if (options.removeFilenameSpaces) exportedFilename = exportedFilename.replace (" ", "_");
}
function exportMaps(mapToExport)
{
doc.activeChannels = doc.componentChannels;
hideAllLayers ();
var layerToExport = doc.layerSets[mapToExport.rgbLayerIndex];
layerToExport.visible = true;
doc.activeLayer = layerToExport;
var selection = doc.selection.selectAll();
doc.selection.copy (true);
var newDoc = app.documents.add(doc.width, doc.height, doc.resolution, exportedFilename + mapToExport.filePostfix);
newDoc.activeChannels = newDoc.componentChannels;
newDoc.paste();
if (mapToExport.exportAlpha)
{
app.activeDocument = doc;
hideAllLayers ();
var layerToExport = doc.layerSets[mapToExport.alphaLayerIndex];
layerToExport.visible = true;
doc.activeLayer = layerToExport;
var selection = doc.selection.selectAll();
doc.selection.copy (true);
app.activeDocument = newDoc;
var alphaChannel = newDoc.channels.add();
newDoc.activeChannels = [alphaChannel];
newDoc.paste();
newDoc.activeChannels = newDoc.componentChannels;
}
if (options.autoSave)
{
saveFile(newDoc);
}
app.activeDocument = doc;
doc.activeChannels = doc.componentChannels;
doc.activeLayer = layerToExport;
}
function hideAllLayers ()
{
for (var i = 0; i < doc.layerSets.length; i++)
{
var currentLayer = doc.layerSets[i];
currentLayer.visible = false;
}
}
function saveFile ()
{
var currentDoc = app.activeDocument;
// Set SaveOptions
tgaSaveOptions = new TargaSaveOptions();
tgaSaveOptions.alphaChannels = currentDoc.channels.length > 3 ? true : false;
tgaSaveOptions.resolution = currentDoc.channels.length > 3 ? TargaBitsPerPixels.THIRTYTWO : TargaBitsPerPixels.TWENTYFOUR;
tgaSaveOptions.rleCompression = false;
// Generate full file path
fullSavePath = new File(exportedFilePath + "/" + currentDoc.name + options.exportedFileExtension);
// Save the file
currentDoc.saveAs(fullSavePath, tgaSaveOptions, true, Extension.LOWERCASE);
if (options.closeDocsOnSave) currentDoc.close(SaveOptions.DONOTSAVECHANGES);
}
function main ()
{
// Check there is an open document, otherwise quit
if (app.documents.length == 0) { alert ("No active document."); return 1; }
// Document caches
doc = app.activeDocument;
docFilename = doc.name.substr(0, doc.name.lastIndexOf('.'));
docPath = doc.saved ? doc.path : Folder.myDocuments;
if (doc.layerSets.length == 0) { alert ("There are no Layer Groups in the active document."); return 1; }
// Other variables
exportedFilename = docFilename;
exportedFilePath = docPath;
errorsLog = "The following errors occurred: \n\n";
var result = showDialog();
if (result == 0) // Form has passed validation
{
getBaseFilename(); // Get the updated filename
for (var map in maps)
{
if (maps[map].selectedToExport() && maps[map].readyToExport())
{
exportMaps(maps[map]);
}
}
alert("All textures exported.");
}
else
{
return 1;
}
return 0;
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment