From 802854da813ccf11ff9cd1d5a7674e6fce42c385 Mon Sep 17 00:00:00 2001 From: Jordan Bleu Date: Fri, 1 Jan 2021 14:20:34 -0600 Subject: [PATCH] First Commit --- Prepare-For-Spine.lua | 189 ++++++++++++++++++++++++++++++++++++++++++ README.md | 37 ++++++++- 2 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 Prepare-For-Spine.lua diff --git a/Prepare-For-Spine.lua b/Prepare-For-Spine.lua new file mode 100644 index 0000000..4ee1a55 --- /dev/null +++ b/Prepare-For-Spine.lua @@ -0,0 +1,189 @@ +--[[ + +Aseprite to Spine Exporter Script +Written by Jordan Bleu + +https://github.com/jordanbleu/aseprite-to-spine + +]] + +-----------------------------------------------[[ Functions ]]----------------------------------------------- +--[[ +Returns a flattened view of +the layers and groups of the sprite. +parent: The sprite or parent layer group +arr: The array to append to +]] +function getLayers(parent, arr) + for i, layer in ipairs(parent.layers) do + if (layer.isGroup) then + arr[#arr + 1] = layer + arr = getLayers(layer, arr) + else + arr[#arr + 1] = layer + end + end + return arr +end + +--[[ +Checks for duplicate layer names, and returns true if any exist (also shows an error to the user) +layers: The flattened view of the sprite layers +]] +function containsDuplicates(layers) + for i, layer in ipairs(layers) do + if (layer.isVisible) then + for j, otherLayer in ipairs(layers) do + -- if we find a duplicate in the list that is not our index + if (j ~= i) and (otherLayer.name == layer.name) and (otherLayer.isVisible) then + app.alert("Found multiple visible layers named '" .. layer.name .. "'. Please use unique layer names or hide one of these layers.") + return true + end + end + end + end + return false +end + +--[[ +Returns an array of each layer's visibility (true / false) +layers: the flattened view of the sprite layers +]] +function captureVisibilityStates(layers) + local visibilities = {} + for i, layer in ipairs(layers) do + visibilities[i] = layer.isVisible + end + return visibilities +end + +--[[ +Hides all layers and groups +layers: The flattened view of the sprite layers +]] +function hideAllLayers(layers) + for i, layer in ipairs(layers) do + if (layer.isGroup) then + layer.isVisible = true + else + layer.isVisible = false + end + end +end + +--[[ +Captures each layer as a separate PNG. Ignores hidden layers. +layers: The flattened view of the sprite layers +sprite: The active sprite +outputDir: the directory the sprite is saved in +visibilityStates: the prior state of each layer's visibility (true / false) +]] +function captureLayers(layers, sprite, outputDir, visibilityStates) + hideAllLayers(layers) + + local separator = app.fs.pathSeparator + + for i, layer in ipairs(layers) do + -- Ignore groups and non-visible layers + if (not layer.isGroup and visibilityStates[i] == true) then + layer.isVisible = true + sprite:saveCopyAs(outputDir .. separator .. "images" .. separator .. layer.name .. ".png") + layer.isVisible = false + end + end +end + +--[[ +Restores layers to their previous visibility state +layers: The flattened view of the sprite layers +visibilityStates: the prior state of each layer's visibility (true / false) +]] +function restoreVisibilities(layers, visibilityStates) + for i, layer in ipairs(layers) do + layer.isVisible = visibilityStates[i] + end +end + + +-----------------------------------------------[[ Main Execution ]]----------------------------------------------- +local activeSprite = app.activeSprite + +if (activeSprite == nil) then + -- If user has no active sprite selected in the UI + app.alert("Please click the sprite you'd like to export") + return +elseif (activeSprite.filename == "") then + -- If the user has created a sprite, but never saved it + app.alert("Please save the current sprite before running this script") + return +end + +local flattenedLayers = getLayers(activeSprite, {}) + +if (containsDuplicates(flattenedLayers)) then + return +end + +-- Get an array containing each layer index and whether it is currently visible +local visibilities = captureVisibilityStates(flattenedLayers) + +-- directory where the sprite is saved +local spritePath = app.fs.filePath(activeSprite.filename) + +-- Saves each sprite layer as a separate .png under the 'images' subdirectory +captureLayers(flattenedLayers, activeSprite, spritePath, visibilities) + +-- Restore the layer's visibilities to how they were before +restoreVisibilities(flattenedLayers, visibilities) + +--[[ +Write out the json file for importing into spine. +(sorry this is so ugly, I didn't want to include a full lua json library) +]] +local spriteFilename = app.fs.fileName(activeSprite.filename) +local jsonFileName = spritePath .. "/" .. spriteFilename .. ".json" +json = io.open(jsonFileName, "w") + +json:write('{') + +-- skeleton +json:write([[ "skeleton": { "images": "images/" }, ]]) + +-- bones +json:write([[ "bones": [ { "name": "root" } ], ]]) + +-- build arrays of json properties for skins and slots +-- we only include layers, not groups +local slotsJson = {} +local skinsJson = {} +local index = 1 + +for i, layer in ipairs(flattenedLayers) do + + if not layer.isGroup then + local name = layer.name + slotsJson[index] = string.format([[ { "name": "%s", "bone": "%s", "attachment": "%s" } ]], name, "root", name) + skinsJson[index] = string.format([[ "%s": { "%s": { "x": 0, "y": 0, "width": 1, "height": 1 } } ]], name, name) + index = index + 1 + end + +end + +-- slots +json:write('"slots": [') +json:write(table.concat(slotsJson, ",")) +json:write("],") + +-- skins +json:write('"skins": {') +json:write('"default": {') +json:write(table.concat(skinsJson, ",")) +json:write('}') +json:write('}') + +-- close the json +json:write("}") + +json:close() + +app.alert("Export completed! Use file '" .. jsonFileName .. "' for importing into Spine.") \ No newline at end of file diff --git a/README.md b/README.md index fc75f75..630fb6f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ -# aseprite-to-spine -This script will import pixel art created in aseprite into Esoteric Spine software for creating skeletal animation. +# aseprite-to-spine + +## Lua Script for importing Aseprite projects into Spine + +## v1.0 + +### Installation + +1. Open Aseprite +2. Go to **File > Scripts > Open Scripts Folder** +3. Drag the included ```Prepare-For-Spine.lua``` file to this directory +4. In Aseprite, click **File > Scripts > Rescan Scripts Folder** + +After following these steps, the "Prepare-For-Spine" script should show up in the list. + +### Usage + +1. Create your sprite just like you would in Photoshop. Each "bone" should be on its own layer. +2. Keep in mind that layer "groups" are ignored when exporting. +3. When you're ready to bring your art into Spine, save your project and run the ```Prepare-For-Spine``` script. This will create a .json file as well as an "images" folder in the directory your aseprite project is saved in. +4. If you get a dialogue requesting permissions for the script, click "give full trust" (it's just requesting permission for the export script to save files). +5. Open Spine and create a new project +6. Click the Spine Logo in the top left to open the file menu, and click **Import Data**. +7. Set up your Skeleton and start creating animations! + +### Known Issues +* Hiding a group of layers will not exclude it from the export. Each layer needs to be shown or hidden individually (group visibility is ignored) +* The Spine will be imported with a name of "{filename}.aseprite". Eventually i'll trim off the .asperite part when I get a sec. +* Not as many options as the Photshop script. Maybe I'll add these in the future but honestly i've never used any of them so we will see. + +### Version History + +#### v1.0 + +Initial Release