-
-
Save keiranlovett/f39b9f70f24930d93609 to your computer and use it in GitHub Desktop.
Photoshop script to export textures from layered PSD
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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