- Изменено
Hello. I've been fiddling around with the export script for Photoshop.
Here's the new script:
EDIT: updated to make the ruler origin optional.
EDIT2: added export directory name option.
EDIT3: fixed ignore layer bug, added "-NOSKIN" tag option, added loading and saving of options.
EDIT4: my bad! needed try / catch block around getCustomOptions when no previous settings exist!
// This script exports photoshop layers as individual images.
// It also write a JSON file that can be imported into Spine
// where the images will be displayed in the same positions.
// Settings.
var savePNGs = true;
var saveTemplate = false;
var saveJSON = true;
var ignoreHiddenLayers = true;
var scaleFactor = 0.5;
var saveSkins = false;
var useRulerOrigin = false;
var exportDirectory = "/images/";
//IDs for custom option saving / loading
const settingsID = stringIDToTypeID("exportOptions");
const savePNGsID = stringIDToTypeID("savePNGs");
const saveTemplateID = stringIDToTypeID("saveTemplate");
const saveJSONID = stringIDToTypeID("saveJSON");
const ignoreHiddenLayersID = stringIDToTypeID("ignoreHiddenLayers");
const saveSkinsID = stringIDToTypeID("saveSkins");
const useRulerOriginID = stringIDToTypeID("useRulerOrigin");
const scaleFactorID = stringIDToTypeID("scaleFactor");
const exportDirectoryID = stringIDToTypeID("exportDirectory");
//try and load previous settings
var testDoc = app.activeDocument;
var exportSettings;
try {
exportSettings = app.getCustomOptions(settingsID);
} catch (e) {
saveSettings();
}
if(typeof exportSettings == "undefined") {
saveSettings();
}
//start the main flow
main();
function main () {
loadSettings();
showDialog();
}
function doExport() {
// Output dir.
var dir = app.activeDocument.path + exportDirectory;
new Folder(dir).create();
var name = decodeURI(app.activeDocument.name);
name = name.substring(0, name.indexOf("."));
//get ruler origin
var xOffSet = 0;
var yOffSet = 0;
if(useRulerOrigin) {
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var desc = executeActionGet(ref);
xOffSet = desc.getInteger(stringIDToTypeID('rulerOriginH')) >> 16;
yOffSet = desc.getInteger(stringIDToTypeID('rulerOriginV')) >> 16;
}
app.activeDocument.duplicate();
if (saveTemplate) {
if (scaleFactor != 1) scaleImage();
var file = new File(dir + "/template");
if (file.exists) file.remove();
activeDocument.saveAs(file, new PNGSaveOptions(), true, Extension.LOWERCASE);
if (scaleFactor != 1) stepHistoryBack();
}
// Collect original layer visibility and hide all layers.
var layers = [];
var layerParents = [];
getLayers(app.activeDocument, layers);
var layerCount = layers.length;
var layerVisibility = {};
//Sort the layers so skin keys don't show multiple times.
/*
layers.sort(function (a, b) {
if (a.parent.name < b.parent.name) return -1;
if (a.parent.name > b.parent.name) return 1;
});
*/
for (var i = layerCount - 1; i >= 0; i
---
) {
var layer = layers[i];
layerVisibility[layer] = layer.visible;
layer.visible = false;
}
// Save JSON.
if (saveJSON || savePNGs) {
var skins = {}, slots = {}, newslots = new Array();
for (var i = layerCount - 1; i >= 0; i
---
) {
//getlayer
var layer = layers[i];
//do we want to use this layer?
if (ignoreHiddenLayers && !layerVisibility[layer]) continue;
//is the layer in a group? if so get the skin name, otherwise default skin
var potentialSkinName = trim(layer.parent.name);
var layerGroupSkin = potentialSkinName.indexOf("-NOSKIN") == -1;
var skinName = (saveSkins && layer.parent.typename == "LayerSet" && layerGroupSkin) ? potentialSkinName : "default";
//does this skin already exist? if not create and clear new skin
var skinLayers = skins[skinName];
if (!skinLayers) skins[skinName] = skinLayers = [];
//store layer in skin
skinLayers[skinLayers.length] = layer;
var slotname = trim(layer.name);
slots[slotname] = true;
}
var json = "{\"bones\":[{\"name\":\"root\"}],\n\"slots\":[\n";
var numSlots = sizeAssocArray(slots);
var curSlot = 0;
for (var slotName in slots) {
if (!slots.hasOwnProperty(slotName)) continue;
json += "\t{\"name\":\"" + slotName + "\",\"bone\":\"root\",\"attachment\":\"" + slotName + "\"}";
curSlot++;
//omit final comma for well formed json
if(curSlot < numSlots) {
json += ",\n";
} else {
json += "\n";
}
}
json += "],\n\"skins\":{\n";
var numSkins = sizeAssocArray(skins);
var curSkin = 0;
for (var skinName in skins) {
if (!skins.hasOwnProperty(skinName)) continue;
json += "\t\"" + skinName + "\":{\n";
var skinLayers = skins[skinName];
var numSkinLayers = skinLayers.length;
var curSkinLayer = 0;
for (var i = skinLayers.length - 1; i >= 0; i
---
) {
var layer = skinLayers[i];
var placeholderName = trim(layer.name);
var attachmentName = skinName == "default" ? placeholderName : skinName + "/" + placeholderName;
var x = app.activeDocument.width.as("px") * scaleFactor;
var y = app.activeDocument.height.as("px") * scaleFactor;
layer.visible = true;
if (!layer.isBackgroundLayer)
app.activeDocument.trim(TrimType.TRANSPARENT, false, true, true, false);
x -= app.activeDocument.width.as("px") * scaleFactor;
y -= app.activeDocument.height.as("px") * scaleFactor;
if (!layer.isBackgroundLayer)
app.activeDocument.trim(TrimType.TRANSPARENT, true, false, false, true);
var width = app.activeDocument.width.as("px") * scaleFactor;
var height = app.activeDocument.height.as("px") * scaleFactor;
// Save image.
if (savePNGs) {
if (scaleFactor != 1) scaleImage();
if (skinName != "default") {
var path = new Folder(dir + "/" + skinName);
if (!path.exists) path.create();
}
var file = new File(dir + "/" + attachmentName);
if (file.exists) file.remove();
activeDocument.saveAs(file, new PNGSaveOptions(), true, Extension.LOWERCASE);
if (scaleFactor != 1) stepHistoryBack();
}
if (!layer.isBackgroundLayer) {
stepHistoryBack();
stepHistoryBack();
}
layer.visible = false;
//center skin part
x += Math.round(width) / 2;
y += Math.round(height) / 2;
//get relative to the photoshop doc ruler origin
if(useRulerOrigin) {
x -= xOffSet * scaleFactor;
//"invert" y
y -= (app.activeDocument.height.as("px") * scaleFactor - yOffSet * scaleFactor);
}
if (attachmentName == placeholderName) {
json += "\t\t\"" + placeholderName + "\":{\"" + placeholderName + "\":{\"x\":" + x + ",\"y\":" + y + ",\"width\":" + Math.round(width) + ",\"height\":" + Math.round(height) + "}}";
} else {
json += "\t\t\"" + placeholderName + "\":{\"" + placeholderName + "\":{\"name\":\"" + attachmentName + "\", \"x\":" + x + ",\"y\":" + y + ",\"width\":" + Math.round(width) + ",\"height\":" + Math.round(height) + "}}";
}
//omit final comma for well formed json
curSkinLayer++;
if(curSkinLayer < numSkinLayers) {
json += ",\n";
} else {
json += "\n";
}
}
json += "\t\}";
//omit final comma for well formed json
curSkin++;
if(curSkin < numSkins) {
json += ",\n";
} else {
json += "\n";
}
}
json += "},\n\"animations\": { \"animation\": {} }}";
if (saveJSON) {
// Write file.
var file = new File(dir + name + ".json");
file.remove();
file.open("w", "TEXT");
file.lineFeed = "\n";
file.write(json);
file.close();
}
}
activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
// Unfinished!
function hasLayerSets (layerset) {
layerset = layerset.layerSets;
for (var i = 0; i < layerset.length; i++)
if (layerset[i].layerSets.length > 0) hasLayerSets(layerset[i]);
}
function scaleImage() {
var imageSize = app.activeDocument.width.as("px");
app.activeDocument.resizeImage(UnitValue(imageSize * scaleFactor, "px"), undefined, 72, ResampleMethod.BICUBICSHARPER);
}
function stepHistoryBack () {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID( "HstS" ), charIDToTypeID( "Ordn" ), charIDToTypeID( "Prvs" ));
desc.putReference(charIDToTypeID( "null" ), ref);
executeAction( charIDToTypeID( "slct" ), desc, DialogModes.NO );
}
function getLayers (layer, collect) {
if (!layer.layers || layer.layers.length == 0) return layer;
for (var i = 0, n = layer.layers.length; i < n; i++) {
// For checking if its an adjustment layer, but it also excludes
// LayerSets so we need to find the different types needed.
//if (layer.layers[i].kind == LayerKind.NORMAL) {
var child = getLayers(layer.layers[i], collect)
if (child) collect.push(child);
//}
}
}
function trim (value) {
return value.replace(/^\s+|\s+$/g, "");
}
function hasFilePath() {
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
return executeActionGet(ref).hasKey(stringIDToTypeID('fileReference'));
}
function showDialog () {
if (!hasFilePath()) {
alert("File path not found.\nYou need to save the document before continuing.");
return;
}
var dialog = new Window("dialog", "Export Layers");
dialog.savePNGs = dialog.add("checkbox", undefined, "Save PNGs");
dialog.savePNGs.value = savePNGs;
dialog.savePNGs.alignment = "left";
dialog.saveTemplate = dialog.add("checkbox", undefined, "Save template PNG");
dialog.saveTemplate.value = saveTemplate;
dialog.saveTemplate.alignment = "left";
dialog.saveJSON = dialog.add("checkbox", undefined, "Save JSON");
dialog.saveJSON.alignment = "left";
dialog.saveJSON.value = saveJSON;
dialog.ignoreHiddenLayers = dialog.add("checkbox", undefined, "Ignore hidden layers");
dialog.ignoreHiddenLayers.alignment = "left";
dialog.ignoreHiddenLayers.value = ignoreHiddenLayers;
dialog.saveSkins = dialog.add("checkbox", undefined, "Save skins");
dialog.saveSkins.alignment = "left";
dialog.saveSkins.value = saveSkins;
dialog.useRulerOrigin = dialog.add("checkbox", undefined, "Use ruler origin as root");
dialog.useRulerOrigin.alignment = "left";
dialog.useRulerOrigin.value = useRulerOrigin;
var directoryGroup = dialog.add("panel", [0, 0, 180, 50], "Export directory");
var directoryText = directoryGroup.add("edittext", [10,10,165,35], exportDirectory);
directoryText.alignment = "left";
directoryText.text = exportDirectory;
var scaleGroup = dialog.add("panel", [0, 0, 180, 50], "Image Scale");
var scaleText = scaleGroup.add("edittext", [10,10,40,30], scaleFactor * 100);
scaleGroup.add("statictext", [45, 12, 100, 20], "%");
var scaleSlider = scaleGroup.add("slider", [60, 10,165,20], scaleFactor * 100, 1, 100);
scaleText.onChanging = function() {
scaleSlider.value = scaleText.text;
if (scaleText.text < 1 || scaleText.text > 100) {
alert("Valid numbers are 1-100.");
scaleText.text = scaleFactor * 100;
scaleSlider.value = scaleFactor * 100;
}
};
scaleSlider.onChanging = function () { scaleText.text = Math.round(scaleSlider.value); };
var confirmGroup = dialog.add("group", [0, 0, 180, 50]);
var runButton = confirmGroup.add("button", [10, 10, 80, 35], "Ok");
var cancelButton = confirmGroup.add("button", [90, 10, 170, 35], "Cancel");
//update the params and save them off on close
function updateParams() {
savePNGs = dialog.savePNGs.value;
saveTemplate = dialog.saveTemplate.value;
saveJSON = dialog.saveJSON.value;
ignoreHiddenLayers = dialog.ignoreHiddenLayers.value;
scaleFactor = scaleSlider.value / 100;
saveSkins = dialog.saveSkins.value;
useRulerOrigin = dialog.useRulerOrigin.value;
exportDirectory = directoryText.text;
};
dialog.onClose = function() {
updateParams();
saveSettings();
};
cancelButton.onClick = function () { this.parent.close(0); return; };
runButton.onClick = function () {
updateParams();
doExport();
this.parent.close(0);
};
dialog.orientation = "column";
dialog.center();
dialog.show();
}
function loadSettings() {
//check for previously saved dialog options
exportSettings = app.getCustomOptions(settingsID);
if(exportSettings.hasKey (savePNGsID))
savePNGs = exportSettings.getBoolean (savePNGsID);
if(exportSettings.hasKey (saveTemplateID))
saveTemplate = exportSettings.getBoolean (saveTemplateID);
if(exportSettings.hasKey (saveJSONID))
saveJSON = exportSettings.getBoolean (saveJSONID);
if(exportSettings.hasKey (ignoreHiddenLayersID))
ignoreHiddenLayers = exportSettings.getBoolean (ignoreHiddenLayersID);
if(exportSettings.hasKey (scaleFactorID))
scaleFactor = exportSettings.getDouble (scaleFactorID);
if(exportSettings.hasKey (saveSkinsID))
saveSkins = exportSettings.getBoolean (saveSkinsID);
if(exportSettings.hasKey (useRulerOriginID))
useRulerOrigin = exportSettings.getBoolean (useRulerOriginID);
if(exportSettings.hasKey (exportDirectoryID))
exportDirectory = exportSettings.getString (exportDirectoryID);
}
function saveSettings() {
//save defaults
var newExportSettings = new ActionDescriptor();
newExportSettings.putBoolean (savePNGsID, savePNGs);
newExportSettings.putBoolean (saveTemplateID, saveTemplate);
newExportSettings.putBoolean (saveJSONID, saveJSON);
newExportSettings.putBoolean (ignoreHiddenLayersID, ignoreHiddenLayers);
newExportSettings.putDouble (scaleFactorID, scaleFactor);
newExportSettings.putBoolean (saveSkinsID, saveSkins);
newExportSettings.putBoolean (useRulerOriginID, useRulerOrigin);
newExportSettings.putString (exportDirectoryID, exportDirectory);
app.putCustomOptions(settingsID,newExportSettings,true);
}
function sizeAssocArray(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
}
Enjoy.
TC.