• Editor
  • New Photoshop export script.

  • Изменено

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.

Related Discussions
...
  • Изменено

Where's the default origin position of the rulers? upper left?

Yeah.

I guess I could put another checkbox in the dialog to revert to the normal behaviour? (new behaviour default off?)

🙂

Good call. I like it.

(I learned how to set the ruler origin just now. And since you mention it in the checkbox, people can naturally check what that is and how to set it to take advantage of the feature.)

Good stuff 🙂 I'll take a look at it later and if I like it with the rulers I might include it in the official script if you're ok with that?

Yeah no worries.

EDIT:

Added option to specify export directory name. Again.. Use it if you want. 🙂

This is also a good feature. 😃

Hello guys!

Today I noticed that when I uncheck "Ignore hidden layers" in the photoshop scrip it doesn't stop ignoring them. Does anyone know if I'm doing something wrong?

Thanks!

Haven't had time to look into the script yet.
If there are any problems with ignoring layers I'll get that fixed as well.

5 дней спустя

Hello.. I've updated the main post with my latest version.

I found the ignore layer bug and fixed it.

The latest version checks if the layergroup / folder name in Photoshop has a "-NOSKIN" tag in it and doesn't create a skin for that layergroup / folder. It's handy for keeping your Photoshop files in order and not having millions of layers in the root of the doc.

Also, the script will now remember what your options were last time round and use those.

Use it if you want.

Enjoy!

Thanks so much for you work on this, tonyc! It's a huge help!

-NOSKIN tag?

tonyc написал

Hello.. I've updated the main post with my latest version.

I found the ignore layer bug and fixed it.

The latest version checks if the layergroup / folder name in Photoshop has a "-NOSKIN" tag in it and doesn't create a skin for that layergroup / folder. It's handy for keeping your Photoshop files in order and not having millions of layers in the root of the doc.

Also, the script will now remember what your options were last time round and use those.

Use it if you want.

Enjoy!

Beat me to doing this 🙂

Thanks.

Pharan написал

Thanks so much for you work on this, tonyc! It's a huge help!

-NOSKIN tag?

Aye, just include the string -NOSKIN (minus no skin) on the folder name in your Photoshop project and the layers within won't be made into skins.

e.g. I have the layers "leftarm" and "rightarm" in the folder "arms-NOSKIN". The slots "leftarm" and "rightarm" are exported to the "default:" skin instead of "arms:" because of the -NOSKIN tag. I haven't fully tested skins inside -NOSKIN folders yet so BEWARE! :o

Cool that you keep updating it 🙂 Not completely sold on the -NOSKIN tag yet though. I would love to update the script for Adobe CC and then use HTML5 to give it a proper looking panel and some buttons to add/remove tags etc. But unfortunately I don't think it will work in older versions of Photoshop then.

This version gets an error "Error 1302: no such element" in Photoshop CS5 on line 27:

var exportSettings = app.getCustomOptions(settingsID);

I think the same code is also in line 363.

I don't think Photoshop CS5 can remember previous settings, or the API changed.


UPDATE:
I used the version with the try-catch block. Seems to work fine in CS5 now. Maybe that was the problem.

@Shiu/@[удалено]/@[удалено]
By the way, do you want to put this on GitHub as a GIST?
https://gist.github.com/

So we can branch and fork and stuff and people can choose the version that works for them while we can still track changes and bugs?

Unfortunately Nate manages the git stuff, I'm sure he can put it up when he is back from vacation.

месяц спустя

Hello,

any chance to get a working export script for Photoshop CS6?

The script works for CS6 that's what I wrote it for.

Any chance to get this script. i tried the pasted one and its not working for me.
maybe i put it wrong into photoshop or give it a wrong extension.
thanks.