From 1f3dd3b0557530ae5eb46c8406a347d3695a8c9d Mon Sep 17 00:00:00 2001 From: Ale Date: Mon, 16 Mar 2026 22:37:38 +0900 Subject: [PATCH 01/25] [Aseprite] Enable Effective Group Visibility During Export - Propagated group visibility downward during recursive traversal. - Combined layer collection and effective-visibility recording into a single recursive pass to improve efficiency. --- aseprite/Prepare-For-Spine.lua | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index b796470..e92e94d 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -6,21 +6,24 @@ https://github.com/jordanbleu/aseprite-to-spine -----------------------------------------------[[ Functions ]]----------------------------------------------- --[[ -Returns a flattened view of -the layers and groups of the sprite. +Flattens the layers of a sprite while considering their effective visibility. parent: The sprite or parent layer group -arr: The array to append to +outLayers: The array to append the flattened layers totally ignoring groups +outVis: The array to append the effective visibility of each layer (true / false) +inheritedVisible: The visibility inherited from parent groups (true / false) ]] -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 +function flattenWithEffectiveVisibility(parent, outLayers, outVis, inheritedVisible) + for _, layer in ipairs(parent.layers) do + -- A layer is effectively visible if it is visible and all of its parent groups are also visible + local effectiveVisible = inheritedVisible and layer.isVisible + + outLayers[#outLayers + 1] = layer + outVis[#outVis + 1] = effectiveVisible + + if layer.isGroup then + flattenWithEffectiveVisibility(layer, outLayers, outVis, effectiveVisible) end end - return arr end --[[ @@ -76,6 +79,7 @@ outputDir: the directory the sprite is saved in visibilityStates: the prior state of each layer's visibility (true / false) ]] function captureLayers(layers, sprite, visibilityStates) + -- First hide all layers so we can selectively show them when we capture them hideAllLayers(layers) local outputDir = app.fs.filePath(sprite.filename) @@ -103,6 +107,7 @@ function captureLayers(layers, sprite, visibilityStates) for i, layer in ipairs(layers) do -- Ignore groups and non-visible layers if (not layer.isGroup and visibilityStates[i] == true) then + -- Set the layer to visible so we can capture it, then set it back to hidden after layer.isVisible = true local cel = layer.cels[1] local cropped = Sprite(sprite) @@ -161,7 +166,9 @@ elseif (activeSprite.filename == "") then return end -local flattenedLayers = getLayers(activeSprite, {}) +local flattenedLayers = {} -- This will be the flattened view of the sprite layers, ignoring groups +local effectiveVisibilities = {} -- This will be the effective visibility of each layer (true / false) +flattenWithEffectiveVisibility(activeSprite, flattenedLayers, effectiveVisibilities, true) if (containsDuplicates(flattenedLayers)) then return @@ -172,7 +179,7 @@ local visibilities = captureVisibilityStates(flattenedLayers) -- Saves each sprite layer as a separate .png under the 'images' subdirectory -- and write out the json file for importing into spine. -captureLayers(flattenedLayers, activeSprite, visibilities) +captureLayers(flattenedLayers, activeSprite, effectiveVisibilities) -- Restore the layer's visibilities to how they were before restoreVisibilities(flattenedLayers, visibilities) \ No newline at end of file From 2001af8e378371c5668d945f3ec8eee8fdcad974 Mon Sep 17 00:00:00 2001 From: Ale Date: Tue, 17 Mar 2026 00:46:49 +0900 Subject: [PATCH 02/25] [Aseprite] Added a new UI options panel - Toggle for Ignore Group Visibility. - export path setting for the output JSON file. --- aseprite/Prepare-For-Spine.lua | 205 ++++++++++++++++++++++++++++----- 1 file changed, 174 insertions(+), 31 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index e92e94d..d8b5f0b 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -6,22 +6,31 @@ https://github.com/jordanbleu/aseprite-to-spine -----------------------------------------------[[ Functions ]]----------------------------------------------- --[[ -Flattens the layers of a sprite while considering their effective visibility. +Flattens the layers of a sprite while allowing optional ignore of parent group visibility. parent: The sprite or parent layer group -outLayers: The array to append the flattened layers totally ignoring groups +outLayers: The array to append the flattened layers outVis: The array to append the effective visibility of each layer (true / false) inheritedVisible: The visibility inherited from parent groups (true / false) +ignoreGroupVisibility: If true, visibility only depends on the layer's own isVisible value ]] -function flattenWithEffectiveVisibility(parent, outLayers, outVis, inheritedVisible) +function flattenWithEffectiveVisibility(parent, outLayers, outVis, inheritedVisible, ignoreGroupVisibility) for _, layer in ipairs(parent.layers) do - -- A layer is effectively visible if it is visible and all of its parent groups are also visible - local effectiveVisible = inheritedVisible and layer.isVisible - + -- Determine the effective visibility of the layer based on its own visibility and the inherited visibility from parent groups + local effectiveVisible + if (ignoreGroupVisibility) then + effectiveVisible = layer.isVisible + else + effectiveVisible = inheritedVisible and layer.isVisible + end + + -- Append the layer and its effective visibility to the output arrays outLayers[#outLayers + 1] = layer outVis[#outVis + 1] = effectiveVisible - + + -- If this layer is a group, recursively flatten its children, passing down the effective visibilityStates if layer.isGroup then - flattenWithEffectiveVisibility(layer, outLayers, outVis, effectiveVisible) + local nextInherited = ignoreGroupVisibility and true or effectiveVisible + flattenWithEffectiveVisibility(layer, outLayers, outVis, nextInherited, ignoreGroupVisibility) end end end @@ -30,12 +39,12 @@ 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) +function containsDuplicates(layers, visibilities) for i, layer in ipairs(layers) do - if (layer.isVisible) then + if (not layer.isGroup and visibilities[i] == true) 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 + if (j ~= i) and (not otherLayer.isGroup) and (otherLayer.name == layer.name) and (visibilities[j] == true) then app.alert("Found multiple visible layers named '" .. layer.name .. "'. Please use unique layer names or hide one of these layers.") return true end @@ -75,24 +84,32 @@ 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) +outputPath: the output json file path +effectiveVisibilities: the prior state of each layer's effectiveVisible visibility (true / false) ]] -function captureLayers(layers, sprite, visibilityStates) +function captureLayers(layers, sprite, effectiveVisibilities, outputPath) + -- Default output path to the sprite-name json in the sprite's directory. + if (outputPath == nil or outputPath == "") then + local defaultOutputDir = app.fs.filePath(sprite.filename) + local defaultSpriteName = app.fs.fileTitle(sprite.filename) + outputPath = defaultOutputDir .. app.fs.pathSeparator .. defaultSpriteName .. ".json" + end + + local outputDir = app.fs.filePath(outputPath) + + -- Create the output directory if it doesn't exist + local separator = app.fs.pathSeparator + local imagesDir = outputDir .. separator .. "images" + app.fs.makeDirectory(imagesDir) + -- First hide all layers so we can selectively show them when we capture them hideAllLayers(layers) - local outputDir = app.fs.filePath(sprite.filename) - local spriteFileName = app.fs.fileTitle(sprite.filename) - - local jsonFileName = outputDir .. app.fs.pathSeparator .. spriteFileName .. ".json" + local jsonFileName = outputPath json = io.open(jsonFileName, "w") - json:write('{') - -- skeleton json:write([[ "skeleton": { "images": "images/" }, ]]) - -- bones json:write([[ "bones": [ { "name": "root" } ], ]]) @@ -102,17 +119,15 @@ function captureLayers(layers, sprite, visibilityStates) local skinsJson = {} local index = 1 - 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 + if (not layer.isGroup and effectiveVisibilities[i] == true) then -- Set the layer to visible so we can capture it, then set it back to hidden after layer.isVisible = true local cel = layer.cels[1] local cropped = Sprite(sprite) cropped:crop(cel.position.x, cel.position.y, cel.bounds.width, cel.bounds.height) - cropped:saveCopyAs(outputDir .. separator .. "images" .. separator .. layer.name .. ".png") + cropped:saveCopyAs(imagesDir .. separator .. layer.name .. ".png") cropped:close() layer.isVisible = false local name = layer.name @@ -126,7 +141,6 @@ function captureLayers(layers, sprite, visibilityStates) json:write('"slots": [') json:write(table.concat(slotsJson, ",")) json:write("],") - -- skins json:write('"skins": {') json:write('"default": {') @@ -136,7 +150,6 @@ function captureLayers(layers, sprite, visibilityStates) -- close the json json:write("}") - json:close() app.alert("Export completed! Use file '" .. jsonFileName .. "' for importing into Spine.") @@ -153,6 +166,128 @@ function restoreVisibilities(layers, visibilityStates) end end + +-----------------------------------------------[[ UI ]]----------------------------------------------- +--[[ +Shows the export options dialog and returns the selected options. +defaultOutputPath: The default json output path +]] +function showExportOptionsDialog(defaultOutputPath) + -- Create a dialog to show export optionsDialog + local optionsDialog = Dialog({ title = "Export To Spine" }) + + -- check: Option to ignore group visibility when determining layer visibilityStates + optionsDialog:check({ + id = "ignoreGroupVisibility", + label = "Ignore Group Visibility", + tooltip = "If checked, layer visibility will only depend on the layer's own visibility setting, and not the visibility of any parent groups.", + selected = false + }) + + -- entry: Output json file path + optionsDialog:entry({ + id = "outputPath", + label = "Output Path", + text = defaultOutputPath + }) + -- button: Open file picker to select output path + optionsDialog:button({ + text = "Select Output Path", + onclick = function() + local currentPath = optionsDialog.data.outputPath + if (currentPath == nil or currentPath == "") then + currentPath = defaultOutputPath + end + + local selectedPath = chooseOutputPath(currentPath) + if (selectedPath ~= nil and selectedPath ~= "") then + optionsDialog:modify({ id = "outputPath", text = selectedPath }) + + -- Nested dialogs can leave stale paint artifacts in some builds; force a redraw. + pcall(function() + app.refresh() + end) + end + end + }) + optionsDialog:newrow() + + -- button: Confirm export + local confirmed = false + optionsDialog:button({ + text = "Export", + focus = true, + onclick = function() + confirmed = true + optionsDialog:close() + end + }) + + -- button: Cancel export + optionsDialog:button({ + text = "Cancel", + onclick = function() + optionsDialog:close() + end + }) + + -- Show the dialog with an initial width of 800px. + optionsDialog:show({ wait = true, bounds = Rectangle(0, 0, 800, 220) }) + if (not confirmed) then + return nil + end + + -- Get the selected options from the dialog + local options = optionsDialog.data + -- Fallback to default path when input is empty. + if (options.outputPath == nil or options.outputPath == "") then + options.outputPath = defaultOutputPath + end + + return options +end + +--[[ +Shows a file picker dialog to choose the output json path. +initialPath: The initial json path to show in the file picker +]] +function chooseOutputPath(initialPath) + -- Default the filename to the same name as the sprite file, but with a .json extension + local spriteFileName = "export" + if (app.activeSprite ~= nil and app.activeSprite.filename ~= "") then + spriteFileName = app.fs.fileTitle(app.activeSprite.filename) + end + local defaultPath = initialPath + if (defaultPath == nil or defaultPath == "") then + local defaultOutputDir = app.fs.filePath(app.activeSprite.filename) + defaultPath = defaultOutputDir .. app.fs.pathSeparator .. spriteFileName .. ".json" + end + + -- Create a file picker dialog to select the output directory and filename + local picker = Dialog("Select Output Path") + picker:file({ + id = "path", + label = "Path", + filename = defaultPath, + save = true + }) + + -- button: Confirm selection + picker:button({ id = "confirm", text = "Confirm" }) + -- button: Cancel selection + picker:button({ id = "cancel", text = "Cancel" }) + picker:show({ wait = true }) + + -- If the user confirmed and provided a path, return it. Otherwise return nil. + local data = picker.data + if (data.confirm and data.path ~= nil and data.path ~= "") then + return data.path + end + + return nil +end + + -----------------------------------------------[[ Main Execution ]]----------------------------------------------- local activeSprite = app.activeSprite @@ -166,11 +301,20 @@ elseif (activeSprite.filename == "") then return end +-- Show the export options dialog UI and get the user's selected options. +local spriteOutputDir = app.fs.filePath(activeSprite.filename) +local spriteOutputName = app.fs.fileTitle(activeSprite.filename) +local defaultOutputPath = spriteOutputDir .. app.fs.pathSeparator .. spriteOutputName .. ".json" +local options = showExportOptionsDialog(defaultOutputPath) +if (options == nil) then + return +end + local flattenedLayers = {} -- This will be the flattened view of the sprite layers, ignoring groups local effectiveVisibilities = {} -- This will be the effective visibility of each layer (true / false) -flattenWithEffectiveVisibility(activeSprite, flattenedLayers, effectiveVisibilities, true) +flattenWithEffectiveVisibility(activeSprite, flattenedLayers, effectiveVisibilities, true, options.ignoreGroupVisibility) -if (containsDuplicates(flattenedLayers)) then +if (containsDuplicates(flattenedLayers, effectiveVisibilities)) then return end @@ -178,8 +322,7 @@ end local visibilities = captureVisibilityStates(flattenedLayers) -- Saves each sprite layer as a separate .png under the 'images' subdirectory --- and write out the json file for importing into spine. -captureLayers(flattenedLayers, activeSprite, effectiveVisibilities) +captureLayers(flattenedLayers, activeSprite, effectiveVisibilities, options.outputPath) -- Restore the layer's visibilities to how they were before restoreVisibilities(flattenedLayers, visibilities) \ No newline at end of file From 0c3f81aab0b3cf622d843ba54a25f0b4a22f1970 Mon Sep 17 00:00:00 2001 From: Ale Date: Tue, 17 Mar 2026 13:47:28 +0900 Subject: [PATCH 03/25] [Aseprite] Added updates to the UI options panel - Toggle for Clear Old Images before export. - Simplified output path selection workflow. - Improved overall UI layout and spacing. --- aseprite/Prepare-For-Spine.lua | 110 ++++++++++++++------------------- 1 file changed, 48 insertions(+), 62 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index d8b5f0b..883a570 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -85,9 +85,10 @@ Captures each layer as a separate PNG. Ignores hidden layers. layers: The flattened view of the sprite layers sprite: The active sprite outputPath: the output json file path +clearOldImages: if true, clear existing images folder before export effectiveVisibilities: the prior state of each layer's effectiveVisible visibility (true / false) ]] -function captureLayers(layers, sprite, effectiveVisibilities, outputPath) +function captureLayers(layers, sprite, effectiveVisibilities, outputPath, clearOldImages) -- Default output path to the sprite-name json in the sprite's directory. if (outputPath == nil or outputPath == "") then local defaultOutputDir = app.fs.filePath(sprite.filename) @@ -100,6 +101,10 @@ function captureLayers(layers, sprite, effectiveVisibilities, outputPath) -- Create the output directory if it doesn't exist local separator = app.fs.pathSeparator local imagesDir = outputDir .. separator .. "images" + -- If the user chose to clear old images, delete the existing images directory and its contents before creating a new one + if (clearOldImages == true) then + deleteDirectoryRecursive(imagesDir) + end app.fs.makeDirectory(imagesDir) -- First hide all layers so we can selectively show them when we capture them @@ -166,6 +171,22 @@ function restoreVisibilities(layers, visibilityStates) end end +--[[ +Deletes a directory and its contents recursively. +path: The path of the directory to delete +]] +function deleteDirectoryRecursive(path) + if (path == nil or path == "") then + return + end + + if (app.fs.pathSeparator == "\\") then + os.execute('rmdir /S /Q "' .. path .. '"') + else + os.execute('rm -rf "' .. path .. '"') + end +end + -----------------------------------------------[[ UI ]]----------------------------------------------- --[[ @@ -180,37 +201,38 @@ function showExportOptionsDialog(defaultOutputPath) optionsDialog:check({ id = "ignoreGroupVisibility", label = "Ignore Group Visibility", - tooltip = "If checked, layer visibility will only depend on the layer's own visibility setting, and not the visibility of any parent groups.", + text = "Use layer visibility only.", selected = false }) - -- entry: Output json file path + -- check: Option to clear old images in the output images directory before export + optionsDialog:check({ + id = "clearOldImages", + label = "Clear Old Images", + text = "Delete existing images first.", + selected = false + }) + optionsDialog:separator({}) + + -- entry: Output json path optionsDialog:entry({ id = "outputPath", label = "Output Path", text = defaultOutputPath }) - -- button: Open file picker to select output path - optionsDialog:button({ - text = "Select Output Path", - onclick = function() - local currentPath = optionsDialog.data.outputPath - if (currentPath == nil or currentPath == "") then - currentPath = defaultOutputPath - end - - local selectedPath = chooseOutputPath(currentPath) + -- file: File picker to select output json path (syncs with entry) + optionsDialog:file({ + id = "outputPathPicker", + title = "Select Output Path", + save = true, + onchange = function() + local selectedPath = optionsDialog.data.outputPathPicker if (selectedPath ~= nil and selectedPath ~= "") then optionsDialog:modify({ id = "outputPath", text = selectedPath }) - - -- Nested dialogs can leave stale paint artifacts in some builds; force a redraw. - pcall(function() - app.refresh() - end) end end }) - optionsDialog:newrow() + optionsDialog:separator({}) -- button: Confirm export local confirmed = false @@ -231,8 +253,12 @@ function showExportOptionsDialog(defaultOutputPath) end }) - -- Show the dialog with an initial width of 800px. - optionsDialog:show({ wait = true, bounds = Rectangle(0, 0, 800, 220) }) + -- Show the dialog with width 500 and centered position. + local dialogWidth = 500 + local dialogHeight = 125 + local x = (app.window.width - dialogWidth) / 2 + local y = (app.window.height - dialogHeight) / 2 + optionsDialog:show({ wait = true, bounds = Rectangle(x, y, dialogWidth, dialogHeight) }) if (not confirmed) then return nil end @@ -247,46 +273,6 @@ function showExportOptionsDialog(defaultOutputPath) return options end ---[[ -Shows a file picker dialog to choose the output json path. -initialPath: The initial json path to show in the file picker -]] -function chooseOutputPath(initialPath) - -- Default the filename to the same name as the sprite file, but with a .json extension - local spriteFileName = "export" - if (app.activeSprite ~= nil and app.activeSprite.filename ~= "") then - spriteFileName = app.fs.fileTitle(app.activeSprite.filename) - end - local defaultPath = initialPath - if (defaultPath == nil or defaultPath == "") then - local defaultOutputDir = app.fs.filePath(app.activeSprite.filename) - defaultPath = defaultOutputDir .. app.fs.pathSeparator .. spriteFileName .. ".json" - end - - -- Create a file picker dialog to select the output directory and filename - local picker = Dialog("Select Output Path") - picker:file({ - id = "path", - label = "Path", - filename = defaultPath, - save = true - }) - - -- button: Confirm selection - picker:button({ id = "confirm", text = "Confirm" }) - -- button: Cancel selection - picker:button({ id = "cancel", text = "Cancel" }) - picker:show({ wait = true }) - - -- If the user confirmed and provided a path, return it. Otherwise return nil. - local data = picker.data - if (data.confirm and data.path ~= nil and data.path ~= "") then - return data.path - end - - return nil -end - -----------------------------------------------[[ Main Execution ]]----------------------------------------------- local activeSprite = app.activeSprite @@ -322,7 +308,7 @@ end local visibilities = captureVisibilityStates(flattenedLayers) -- Saves each sprite layer as a separate .png under the 'images' subdirectory -captureLayers(flattenedLayers, activeSprite, effectiveVisibilities, options.outputPath) +captureLayers(flattenedLayers, activeSprite, effectiveVisibilities, options.outputPath, options.clearOldImages) -- Restore the layer's visibilities to how they were before restoreVisibilities(flattenedLayers, visibilities) \ No newline at end of file From 1d60a2421f2340d9659cb47118baadb229f3c6e5 Mon Sep 17 00:00:00 2001 From: Ale Date: Tue, 17 Mar 2026 19:55:41 +0900 Subject: [PATCH 04/25] [Aseprite] Added updates to the UI options panel - Coordinate origin is now configurable (X/Y), with range support for [0,1]. - Added a toggle to keep coordinate values as integers (drop decimal part). - Added quick access to open the exported file location after export completion. --- aseprite/Prepare-For-Spine.lua | 142 +++++++++++++++++++++++++++++---- 1 file changed, 125 insertions(+), 17 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 883a570..cd5c9c2 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -84,11 +84,13 @@ end Captures each layer as a separate PNG. Ignores hidden layers. layers: The flattened view of the sprite layers sprite: The active sprite +effectiveVisibilities: the prior state of each layer's effectiveVisible visibility (true / false) outputPath: the output json file path clearOldImages: if true, clear existing images folder before export -effectiveVisibilities: the prior state of each layer's effectiveVisible visibility (true / false) +originX, originY: the user-defined origin point for the exported Spine skeleton, as a percentage of the sprite's width and height (range 0-1) +roundCoordinatesToInteger: if true, rounds the attachment coordinates to the nearest integer instead of keeping decimals (not recommended for pixel art) ]] -function captureLayers(layers, sprite, effectiveVisibilities, outputPath, clearOldImages) +function captureLayers(layers, sprite, effectiveVisibilities, outputPath, clearOldImages, originX, originY, roundCoordinatesToInteger) -- Default output path to the sprite-name json in the sprite's directory. if (outputPath == nil or outputPath == "") then local defaultOutputDir = app.fs.filePath(sprite.filename) @@ -136,8 +138,18 @@ function captureLayers(layers, sprite, effectiveVisibilities, outputPath, clearO cropped:close() layer.isVisible = false local name = layer.name + -- Calculate the attachment position based on the cel position, cel bounds, sprite bounds, and the user-defined originX and originY. + local attachmentX = cel.bounds.width / 2 + cel.position.x - sprite.bounds.width * originX + local attachmentY = sprite.bounds.height * (1 - originY) - cel.position.y - cel.bounds.height / 2 slotsJson[index] = string.format([[ { "name": "%s", "bone": "%s", "attachment": "%s" } ]], name, "root", name) - skinsJson[index] = string.format([[ "%s": { "%s": { "x": %d, "y": %d, "width": 1, "height": 1 } } ]], name, name, cel.bounds.width/2 + cel.position.x - sprite.bounds.width/2, sprite.bounds.height - cel.position.y - cel.bounds.height/2) + -- If roundCoordinatesToInteger is true, round the attachmentX and attachmentY to the nearest integer using math.modf. Otherwise, keep the decimal values with 3 decimal places. + if (roundCoordinatesToInteger == true) then + attachmentX = math.modf(attachmentX) + attachmentY = math.modf(attachmentY) + skinsJson[index] = string.format([[ "%s": { "%s": { "x": %d, "y": %d, "width": 1, "height": 1 } } ]], name, name, attachmentX, attachmentY) + else + skinsJson[index] = string.format([[ "%s": { "%s": { "x": %.3f, "y": %.3f, "width": 1, "height": 1 } } ]], name, name, attachmentX, attachmentY) + end index = index + 1 end end @@ -157,7 +169,8 @@ function captureLayers(layers, sprite, effectiveVisibilities, outputPath, clearO json:write("}") json:close() - app.alert("Export completed! Use file '" .. jsonFileName .. "' for importing into Spine.") + -- Show export completion dialog + showExportCompletedDialog(jsonFileName) end --[[ @@ -187,6 +200,25 @@ function deleteDirectoryRecursive(path) end end +--[[ +Opens the OS file explorer and selects the exported file when possible. +filePath: The full path of the exported file +]] +function openFileLocation(filePath) + if (filePath == nil or filePath == "") then + return + end + + if (app.fs.pathSeparator == "\\") then + os.execute('explorer /select,"' .. filePath .. '"') + else + local dirPath = app.fs.filePath(filePath) + if (app.fs.pathSeparator == "/") then + os.execute('xdg-open "' .. dirPath .. '"') + end + end +end + -----------------------------------------------[[ UI ]]----------------------------------------------- --[[ @@ -197,19 +229,29 @@ function showExportOptionsDialog(defaultOutputPath) -- Create a dialog to show export optionsDialog local optionsDialog = Dialog({ title = "Export To Spine" }) - -- check: Option to ignore group visibility when determining layer visibilityStates - optionsDialog:check({ - id = "ignoreGroupVisibility", - label = "Ignore Group Visibility", - text = "Use layer visibility only.", - selected = false + -- label: Coordinate settings section + optionsDialog:label({ + id = "coordinateSettings", + label = "Coordinate Settings", + text = "Set which position is used as the Spine origin (0,0). Range: [0,1]." }) - - -- check: Option to clear old images in the output images directory before export + -- number: Coordinate origin X and Y (0-1). + optionsDialog:number({ + id = "originX", + label = "Origin (X,Y)", + text = "0.5", + decimals = 3, + }) + :number({ + id = "originY", + text = "0", + decimals = 3, + }) + -- check: Option to round attachment coordinates to integers instead of keeping decimals optionsDialog:check({ - id = "clearOldImages", - label = "Clear Old Images", - text = "Delete existing images first.", + id = "roundCoordinatesToInteger", + label = "Round Coordinates To Integer", + text = "Drop decimal pixels, May misalign pixels; not recommended for pixel art.", selected = false }) optionsDialog:separator({}) @@ -224,6 +266,8 @@ function showExportOptionsDialog(defaultOutputPath) optionsDialog:file({ id = "outputPathPicker", title = "Select Output Path", + filename = defaultOutputPath, + text = "Select Output Path", save = true, onchange = function() local selectedPath = optionsDialog.data.outputPathPicker @@ -234,6 +278,23 @@ function showExportOptionsDialog(defaultOutputPath) }) optionsDialog:separator({}) + -- check: Option to ignore group visibility when determining layer visibilityStates + optionsDialog:check({ + id = "ignoreGroupVisibility", + label = "Ignore Group Visibility", + text = "Use layer visibility only.", + selected = false + }) + + -- check: Option to clear old images in the output images directory before export + optionsDialog:check({ + id = "clearOldImages", + label = "Clear Old Images", + text = "Delete existing images first.", + selected = false + }) + optionsDialog:separator({}) + -- button: Confirm export local confirmed = false optionsDialog:button({ @@ -255,7 +316,7 @@ function showExportOptionsDialog(defaultOutputPath) -- Show the dialog with width 500 and centered position. local dialogWidth = 500 - local dialogHeight = 125 + local dialogHeight = 190 local x = (app.window.width - dialogWidth) / 2 local y = (app.window.height - dialogHeight) / 2 optionsDialog:show({ wait = true, bounds = Rectangle(x, y, dialogWidth, dialogHeight) }) @@ -269,10 +330,48 @@ function showExportOptionsDialog(defaultOutputPath) if (options.outputPath == nil or options.outputPath == "") then options.outputPath = defaultOutputPath end + + -- Parse originX and originY as numbers, and fallback to defaults if parsing fails or values are out of range + local parsedOriginX = tonumber(options.originX) + local parsedOriginY = tonumber(options.originY) + options.originX = (parsedOriginX ~= nil and parsedOriginX >= 0 and parsedOriginX <= 1) and parsedOriginX or 0.5 + options.originY = (parsedOriginY ~= nil and parsedOriginY >= 0 and parsedOriginY <= 1) and parsedOriginY or 0 return options end +--[[ +Shows export completion dialog with action to open the exported file location. +jsonFileName: The exported json file path +]] +function showExportCompletedDialog(jsonFileName) + local completedDialog = Dialog({ title = "Export Completed" }) + + completedDialog:label({ + id = "message", + text = "Export completed! Use this file for importing into Spine:\n" .. jsonFileName + }) + + completedDialog:newrow() + completedDialog:button({ + text = "Open File Folder", + onclick = function() + openFileLocation(jsonFileName) + completedDialog:close() + end + }) + + completedDialog:button({ + text = "OK", + focus = true, + onclick = function() + completedDialog:close() + end + }) + + completedDialog:show({ wait = true }) +end + -----------------------------------------------[[ Main Execution ]]----------------------------------------------- local activeSprite = app.activeSprite @@ -308,7 +407,16 @@ end local visibilities = captureVisibilityStates(flattenedLayers) -- Saves each sprite layer as a separate .png under the 'images' subdirectory -captureLayers(flattenedLayers, activeSprite, effectiveVisibilities, options.outputPath, options.clearOldImages) +captureLayers( + flattenedLayers, + activeSprite, + effectiveVisibilities, + options.outputPath, + options.clearOldImages, + options.originX, + options.originY, + options.roundCoordinatesToInteger +) -- Restore the layer's visibilities to how they were before restoreVisibilities(flattenedLayers, visibilities) \ No newline at end of file From a6c494b350d84254cc671440647860eea991f925 Mon Sep 17 00:00:00 2001 From: Ale Date: Tue, 17 Mar 2026 22:21:23 +0900 Subject: [PATCH 05/25] [Aseprite] Added export workflow and coordinate UI improvements - Added origin coordinate preset buttons for quick setup (Center, Bottom-Center, Bottom-Left, Top-Left). - Added real-time clamping for origin coordinate inputs, limiting values to the [0,1] range. - Added export completion dialog warnings that list any file paths that failed to write during export. --- aseprite/Prepare-For-Spine.lua | 276 +++++++++++++++++++++++++-------- 1 file changed, 215 insertions(+), 61 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index cd5c9c2..745a263 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -40,17 +40,46 @@ Checks for duplicate layer names, and returns true if any exist (also shows an e layers: The flattened view of the sprite layers ]] function containsDuplicates(layers, visibilities) + local nameCounts = {} -- Map of layer name to count + local duplicateNames = {} -- List of layer duplicates names + -- Iterate through the layers and count the occurrences of each name among visible layers for i, layer in ipairs(layers) do if (not layer.isGroup and visibilities[i] == true) 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 (not otherLayer.isGroup) and (otherLayer.name == layer.name) and (visibilities[j] == true) then - app.alert("Found multiple visible layers named '" .. layer.name .. "'. Please use unique layer names or hide one of these layers.") - return true - end + local name = layer.name + local count = (nameCounts[name] or 0) + 1 + nameCounts[name] = count + if (count == 2) then + duplicateNames[#duplicateNames + 1] = name end end end + + -- If any duplicates were found, show one dialog listing all duplicate names. + if (#duplicateNames > 0) then + table.sort(duplicateNames) + local duplicateDialog = Dialog({ title = "Duplicate Layer Names" }) + duplicateDialog:label({ + id = "message", + text = "Found duplicate visible layer names, Please use unique names:" + }) + for _, duplicateName in ipairs(duplicateNames) do + duplicateDialog:newrow() + duplicateDialog:label({ + text = duplicateName .. " ▸ Count: " .. nameCounts[duplicateName] + }) + end + duplicateDialog:newrow() + duplicateDialog:button({ + text = "OK", + focus = true, + onclick = function() + duplicateDialog:close() + end + }) + duplicateDialog:show({ wait = true }) + return true + end + return false end @@ -90,52 +119,91 @@ clearOldImages: if true, clear existing images folder before export originX, originY: the user-defined origin point for the exported Spine skeleton, as a percentage of the sprite's width and height (range 0-1) roundCoordinatesToInteger: if true, rounds the attachment coordinates to the nearest integer instead of keeping decimals (not recommended for pixel art) ]] -function captureLayers(layers, sprite, effectiveVisibilities, outputPath, clearOldImages, originX, originY, roundCoordinatesToInteger) +function captureLayers( + layers, + sprite, + effectiveVisibilities, + outputPath, + clearOldImages, + originX, + originY, + roundCoordinatesToInteger) -- Default output path to the sprite-name json in the sprite's directory. if (outputPath == nil or outputPath == "") then local defaultOutputDir = app.fs.filePath(sprite.filename) local defaultSpriteName = app.fs.fileTitle(sprite.filename) outputPath = defaultOutputDir .. app.fs.pathSeparator .. defaultSpriteName .. ".json" end - local outputDir = app.fs.filePath(outputPath) - - -- Create the output directory if it doesn't exist + -- Create the output images directory if it doesn't exist local separator = app.fs.pathSeparator local imagesDir = outputDir .. separator .. "images" - -- If the user chose to clear old images, delete the existing images directory and its contents before creating a new one + -- If the user chose to clear old images, delete the existing images directory if (clearOldImages == true) then deleteDirectoryRecursive(imagesDir) end app.fs.makeDirectory(imagesDir) + -- record any failed paths so we can show an error to the user at the end. + local failedPaths = {} + local function addFailedPath(path) + if (path == nil or path == "") then + return + end + for _, existing in ipairs(failedPaths) do + if (existing == path) then + return + end + end + failedPaths[#failedPaths + 1] = path + end + -- Probe images directory write permission. + local probePath = imagesDir .. separator .. ".aseprite_write_probe.tmp" + local probeFile = io.open(probePath, "w") + if (probeFile ~= nil) then + probeFile:close() + os.remove(probePath) + else + addFailedPath(imagesDir) + end + -- First hide all layers so we can selectively show them when we capture them hideAllLayers(layers) + -- Create and open the output json file for writing local jsonFileName = outputPath - json = io.open(jsonFileName, "w") - json:write('{') - -- skeleton - json:write([[ "skeleton": { "images": "images/" }, ]]) - -- bones - json:write([[ "bones": [ { "name": "root" } ], ]]) - + local json = io.open(jsonFileName, "w") + if (json == nil) then + addFailedPath(jsonFileName) + else + json:write('{') + -- skeleton + json:write([[ "skeleton": { "images": "images/" }, ]]) + -- bones + json:write([[ "bones": [ { "name": "root" } ], ]]) + end -- 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(layers) do -- Ignore groups and non-visible layers if (not layer.isGroup and effectiveVisibilities[i] == true) then -- Set the layer to visible so we can capture it, then set it back to hidden after layer.isVisible = true local cel = layer.cels[1] - local cropped = Sprite(sprite) - cropped:crop(cel.position.x, cel.position.y, cel.bounds.width, cel.bounds.height) - cropped:saveCopyAs(imagesDir .. separator .. layer.name .. ".png") - cropped:close() + local imagePath = imagesDir .. separator .. layer.name .. ".png" + local savedOk = false + savedOk = pcall(function() + local cropped = Sprite(sprite) + cropped:crop(cel.position.x, cel.position.y, cel.bounds.width, cel.bounds.height) + cropped:saveCopyAs(imagePath) + cropped:close() + end) + if (savedOk ~= true) then + addFailedPath(imagePath) + end layer.isVisible = false local name = layer.name -- Calculate the attachment position based on the cel position, cel bounds, sprite bounds, and the user-defined originX and originY. @@ -155,22 +223,24 @@ function captureLayers(layers, sprite, effectiveVisibilities, outputPath, clearO 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() + if (json ~= nil) then + 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() + end -- Show export completion dialog - showExportCompletedDialog(jsonFileName) + showExportCompletedDialog(jsonFileName, failedPaths) end --[[ @@ -220,7 +290,7 @@ function openFileLocation(filePath) end ------------------------------------------------[[ UI ]]----------------------------------------------- +-----------------------------------------------[[ UI Functions ]]----------------------------------------------- --[[ Shows the export options dialog and returns the selected options. defaultOutputPath: The default json output path @@ -229,7 +299,26 @@ function showExportOptionsDialog(defaultOutputPath) -- Create a dialog to show export optionsDialog local optionsDialog = Dialog({ title = "Export To Spine" }) - -- label: Coordinate settings section + --#region Coordinate Settings + + -- function: Clamps a number to the range [0,1]. + local function clampTo01(value) + if (value < 0) then + return 0 + elseif (value > 1) then + return 1 + end + return value + end + -- function: Clamps the input field for originX and originY to the range [0,1]. + local function clampOriginField(fieldId, fallback) + local parsed = tonumber(optionsDialog.data[fieldId]) + if (parsed == nil) then + parsed = fallback + end + optionsDialog:modify({ id = fieldId, text = string.format("%.3f", clampTo01(parsed)) }) + end + optionsDialog:label({ id = "coordinateSettings", label = "Coordinate Settings", @@ -239,14 +328,53 @@ function showExportOptionsDialog(defaultOutputPath) optionsDialog:number({ id = "originX", label = "Origin (X,Y)", - text = "0.5", + text = "0.500", decimals = 3, + onchange = function() + clampOriginField("originX", 0.5) + end }) :number({ id = "originY", - text = "0", + text = "0.000", decimals = 3, + onchange = function() + clampOriginField("originY", 0) + end }) + + -- button: Presets for common origin settings (center, bottom-center, bottom-left, top-left) + local function setOriginPreset(x, y) + optionsDialog:modify({ id = "originX", text = string.format("%.3f", x) }) + optionsDialog:modify({ id = "originY", text = string.format("%.3f", y) }) + end + optionsDialog:newrow() + optionsDialog:button({ + text = "Center", + onclick = function() + setOriginPreset(0.5, 0.5) + end + }) + optionsDialog:button({ + text = "Bottom-Center", + onclick = function() + setOriginPreset(0.5, 0) + end + }) + optionsDialog:button({ + text = "Bottom-Left", + onclick = function() + setOriginPreset(0, 0) + end + }) + optionsDialog:button({ + text = "Top-Left", + onclick = function() + setOriginPreset(0, 1) + end + }) + optionsDialog:newrow() + -- check: Option to round attachment coordinates to integers instead of keeping decimals optionsDialog:check({ id = "roundCoordinatesToInteger", @@ -255,7 +383,9 @@ function showExportOptionsDialog(defaultOutputPath) selected = false }) optionsDialog:separator({}) - +--#endregion + + --#region Output Path Settings -- entry: Output json path optionsDialog:entry({ id = "outputPath", @@ -277,7 +407,9 @@ function showExportOptionsDialog(defaultOutputPath) end }) optionsDialog:separator({}) + --#endregion + --#region Other Settings -- check: Option to ignore group visibility when determining layer visibilityStates optionsDialog:check({ id = "ignoreGroupVisibility", @@ -294,7 +426,9 @@ function showExportOptionsDialog(defaultOutputPath) selected = false }) optionsDialog:separator({}) - + --#endregion + + --#region Execution Buttons -- button: Confirm export local confirmed = false optionsDialog:button({ @@ -313,29 +447,27 @@ function showExportOptionsDialog(defaultOutputPath) optionsDialog:close() end }) + --#endregion - -- Show the dialog with width 500 and centered position. - local dialogWidth = 500 - local dialogHeight = 190 - local x = (app.window.width - dialogWidth) / 2 - local y = (app.window.height - dialogHeight) / 2 - optionsDialog:show({ wait = true, bounds = Rectangle(x, y, dialogWidth, dialogHeight) }) + -- Show the dialog and wait for user input. + optionsDialog:show({ wait = true}) if (not confirmed) then return nil end + --#region options Data Extraction -- Get the selected options from the dialog local options = optionsDialog.data -- Fallback to default path when input is empty. if (options.outputPath == nil or options.outputPath == "") then options.outputPath = defaultOutputPath end - -- Parse originX and originY as numbers, and fallback to defaults if parsing fails or values are out of range local parsedOriginX = tonumber(options.originX) local parsedOriginY = tonumber(options.originY) - options.originX = (parsedOriginX ~= nil and parsedOriginX >= 0 and parsedOriginX <= 1) and parsedOriginX or 0.5 - options.originY = (parsedOriginY ~= nil and parsedOriginY >= 0 and parsedOriginY <= 1) and parsedOriginY or 0 + options.originX = clampTo01(parsedOriginX or 0.5) + options.originY = clampTo01(parsedOriginY or 0) + --#endregion return options end @@ -343,16 +475,38 @@ end --[[ Shows export completion dialog with action to open the exported file location. jsonFileName: The exported json file path +failedPaths: The list of file/directory paths that failed to write ]] -function showExportCompletedDialog(jsonFileName) +function showExportCompletedDialog(jsonFileName, failedPaths) local completedDialog = Dialog({ title = "Export Completed" }) + -- Show the exported file path completedDialog:label({ id = "message", - text = "Export completed! Use this file for importing into Spine:\n" .. jsonFileName + text = "Export completed! Use this file for importing into Spine:" }) + completedDialog:newrow() + completedDialog:label({ + text = jsonFileName + }) + + -- If there were any failed paths, show an error message and list the failed paths. + if failedPaths ~= nil and #failedPaths > 0 then + completedDialog:newrow() + completedDialog:label({ + text = "Failed to write:" + }) + -- List each failed path + for _, path in ipairs(failedPaths) do + completedDialog:newrow() + completedDialog:label({ + text = path + }) + end + end completedDialog:newrow() + -- Button to open the file location in the OS file explorer completedDialog:button({ text = "Open File Folder", onclick = function() @@ -360,7 +514,7 @@ function showExportCompletedDialog(jsonFileName) completedDialog:close() end }) - + -- Button to close the dialog completedDialog:button({ text = "OK", focus = true, @@ -408,12 +562,12 @@ local visibilities = captureVisibilityStates(flattenedLayers) -- Saves each sprite layer as a separate .png under the 'images' subdirectory captureLayers( - flattenedLayers, - activeSprite, - effectiveVisibilities, - options.outputPath, - options.clearOldImages, - options.originX, + flattenedLayers, + activeSprite, + effectiveVisibilities, + options.outputPath, + options.clearOldImages, + options.originX, options.originY, options.roundCoordinatesToInteger ) From df59492d89eaad6fd8ac272721ecc94939e928f1 Mon Sep 17 00:00:00 2001 From: Ale Date: Tue, 17 Mar 2026 23:10:37 +0900 Subject: [PATCH 06/25] [Aseprite] Added persistent UI configuration cache - Added configuration caching for all export options, so settings are restored automatically on next launch. - Added a Reset Config button to restore default values and clear cached settings. --- aseprite/Prepare-For-Spine.lua | 138 +++++++++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 17 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 745a263..9b03d40 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -293,12 +293,35 @@ end -----------------------------------------------[[ UI Functions ]]----------------------------------------------- --[[ Shows the export options dialog and returns the selected options. -defaultOutputPath: The default json output path ]] -function showExportOptionsDialog(defaultOutputPath) +function showExportOptionsDialog() -- Create a dialog to show export optionsDialog local optionsDialog = Dialog({ title = "Export To Spine" }) + -- Load cached options or use defaults if no cache exists + local activeSprite = app.activeSprite + local spriteOutputDir = app.fs.filePath(activeSprite.filename) + local spriteOutputName = app.fs.fileTitle(activeSprite.filename) + local defaultOutputPath = spriteOutputDir .. app.fs.pathSeparator .. spriteOutputName .. ".json" + local cachedOptions, configPath = loadCachedOptions(defaultOutputPath) + + --#region Other Buttons + -- button: Resets all options to their default values + optionsDialog:button({ + text = "Reset Config", + onclick = function() + optionsDialog:modify({ id = "originX", text = string.format("%.3f", 0.5) }) + optionsDialog:modify({ id = "originY", text = string.format("%.3f", 0) }) + optionsDialog:modify({ id = "roundCoordinatesToInteger", selected = false }) + optionsDialog:modify({ id = "outputPath", text = defaultOutputPath }) + optionsDialog:modify({ id = "ignoreGroupVisibility", selected = false }) + optionsDialog:modify({ id = "clearOldImages", selected = false }) + end + }) + + optionsDialog:separator({}) + --#endregion + --#region Coordinate Settings -- function: Clamps a number to the range [0,1]. @@ -328,7 +351,7 @@ function showExportOptionsDialog(defaultOutputPath) optionsDialog:number({ id = "originX", label = "Origin (X,Y)", - text = "0.500", + text = string.format("%.3f", cachedOptions.originX), decimals = 3, onchange = function() clampOriginField("originX", 0.5) @@ -336,7 +359,7 @@ function showExportOptionsDialog(defaultOutputPath) }) :number({ id = "originY", - text = "0.000", + text = string.format("%.3f", cachedOptions.originY), decimals = 3, onchange = function() clampOriginField("originY", 0) @@ -380,7 +403,7 @@ function showExportOptionsDialog(defaultOutputPath) id = "roundCoordinatesToInteger", label = "Round Coordinates To Integer", text = "Drop decimal pixels, May misalign pixels; not recommended for pixel art.", - selected = false + selected = cachedOptions.roundCoordinatesToInteger }) optionsDialog:separator({}) --#endregion @@ -390,13 +413,13 @@ function showExportOptionsDialog(defaultOutputPath) optionsDialog:entry({ id = "outputPath", label = "Output Path", - text = defaultOutputPath + text = cachedOptions.outputPath }) -- file: File picker to select output json path (syncs with entry) optionsDialog:file({ id = "outputPathPicker", title = "Select Output Path", - filename = defaultOutputPath, + filename = cachedOptions.outputPath, text = "Select Output Path", save = true, onchange = function() @@ -415,7 +438,7 @@ function showExportOptionsDialog(defaultOutputPath) id = "ignoreGroupVisibility", label = "Ignore Group Visibility", text = "Use layer visibility only.", - selected = false + selected = cachedOptions.ignoreGroupVisibility }) -- check: Option to clear old images in the output images directory before export @@ -423,7 +446,7 @@ function showExportOptionsDialog(defaultOutputPath) id = "clearOldImages", label = "Clear Old Images", text = "Delete existing images first.", - selected = false + selected = cachedOptions.clearOldImages }) optionsDialog:separator({}) --#endregion @@ -451,9 +474,6 @@ function showExportOptionsDialog(defaultOutputPath) -- Show the dialog and wait for user input. optionsDialog:show({ wait = true}) - if (not confirmed) then - return nil - end --#region options Data Extraction -- Get the selected options from the dialog @@ -467,8 +487,16 @@ function showExportOptionsDialog(defaultOutputPath) local parsedOriginY = tonumber(options.originY) options.originX = clampTo01(parsedOriginX or 0.5) options.originY = clampTo01(parsedOriginY or 0) + + -- Save the options to cache so they will be remembered next time the dialog is opened. + saveCachedOptions(configPath, options) --#endregion + -- If the user did not confirm the export (clicked Cancel or closed the dialog), return nil. + if (not confirmed) then + return nil + end + return options end @@ -526,10 +554,89 @@ function showExportCompletedDialog(jsonFileName, failedPaths) completedDialog:show({ wait = true }) end +--#region Config Caching Functions + +--[[ +Parses a string boolean value. +value: The string to parse ("true" or "false") +fallback: The value to return if parsing fails (not "true" or "false") +]] +function parseBool(value, fallback) + if (value == "true") then + return true + elseif (value == "false") then + return false + end + return fallback +end + +--[[ +Loads cached UI options from disk. +defaultOutputPath: The default output path to use if no cached path is found +]] +function loadCachedOptions(defaultOutputPath) + local cached = { + originX = 0.5, + originY = 0, + roundCoordinatesToInteger = false, + outputPath = defaultOutputPath, + ignoreGroupVisibility = false, + clearOldImages = false + } + -- Create a config directory under the user's Aseprite config path, and define the config file path + local configDir = app.fs.joinPath(app.fs.filePath(app.fs.userConfigPath), "Cache") + app.fs.makeDirectory(configDir) + local configPath = app.fs.joinPath(configDir, "Prepare-For-Spine-Config.json") + local configFile = io.open(configPath, "r") + if (configFile == nil) then + return cached, configPath + end + + local raw = {} + for line in configFile:lines() do + local key, value = string.match(line, "^([^=]+)=(.*)$") + if (key ~= nil and value ~= nil) then + raw[key] = value + end + end + configFile:close() + + cached.originX = tonumber(raw.originX) or cached.originX + cached.originY = tonumber(raw.originY) or cached.originY + cached.roundCoordinatesToInteger = parseBool(raw.roundCoordinatesToInteger, cached.roundCoordinatesToInteger) + cached.ignoreGroupVisibility = parseBool(raw.ignoreGroupVisibility, cached.ignoreGroupVisibility) + cached.clearOldImages = parseBool(raw.clearOldImages, cached.clearOldImages) + if (raw.outputPath ~= nil and raw.outputPath ~= "") then + cached.outputPath = raw.outputPath + end + + return cached, configPath +end + +--[[ +Saves UI options to cache file. +configPath: The path to the config file to save +options: The options to save +]] +function saveCachedOptions(configPath, options) + local configFile = io.open(configPath, "w") + if (configFile == nil) then + return + end + + configFile:write("originX=" .. string.format("%.3f", options.originX) .. "\n") + configFile:write("originY=" .. string.format("%.3f", options.originY) .. "\n") + configFile:write("roundCoordinatesToInteger=" .. tostring(options.roundCoordinatesToInteger == true) .. "\n") + configFile:write("outputPath=" .. (options.outputPath or "") .. "\n") + configFile:write("ignoreGroupVisibility=" .. tostring(options.ignoreGroupVisibility == true) .. "\n") + configFile:write("clearOldImages=" .. tostring(options.clearOldImages == true) .. "\n") + configFile:close() +end +--#endregion + -----------------------------------------------[[ 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") @@ -541,10 +648,7 @@ elseif (activeSprite.filename == "") then end -- Show the export options dialog UI and get the user's selected options. -local spriteOutputDir = app.fs.filePath(activeSprite.filename) -local spriteOutputName = app.fs.fileTitle(activeSprite.filename) -local defaultOutputPath = spriteOutputDir .. app.fs.pathSeparator .. spriteOutputName .. ".json" -local options = showExportOptionsDialog(defaultOutputPath) +local options = showExportOptionsDialog() if (options == nil) then return end From ef2f7dd8e02e72f1ae7625c35ff07a2b82d29407 Mon Sep 17 00:00:00 2001 From: Ale Date: Wed, 18 Mar 2026 00:53:11 +0900 Subject: [PATCH 07/25] [Aseprite] Update README to v1.2 - Update version to v1.2. - Add configuration UI screenshots and usage guide. - Add Chinese (ZH) README. --- aseprite/Images/image.png | Bin 0 -> 9005 bytes aseprite/README.md | 97 ++++++++++++++++++++++++++++----- aseprite/README_cn.md | 112 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 aseprite/Images/image.png create mode 100644 aseprite/README_cn.md diff --git a/aseprite/Images/image.png b/aseprite/Images/image.png new file mode 100644 index 0000000000000000000000000000000000000000..6e2e6647735cb253cb0d2264e3d6cdf3bd507034 GIT binary patch literal 9005 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 Mv%AxcPfmRCzsF^8A^-pY literal 0 HcmV?d00001 diff --git a/aseprite/README.md b/aseprite/README.md index 90154d2..e821a78 100644 --- a/aseprite/README.md +++ b/aseprite/README.md @@ -1,14 +1,15 @@ -### Update - This has been added to the official Spine Scripts repository: +### Update - This has been added to the official Spine Scripts repository - https://github.com/EsotericSoftware/spine-scripts + ___ -# aseprite-to-spine +# aseprite-to-spine +[中文版 文档](README_cn.md) ## Lua Script for importing Aseprite projects into Spine -## v1.1 +## v1.2 ### Installation @@ -19,26 +20,92 @@ ___ After following these steps, the "Prepare-For-Spine" script should show up in the list. -### Usage +### 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! +#### 「Aseprite Export」 + +1. Create your sprite just like you would in Photoshop. Each "bone" should be on its own layer. +2. When you're ready to bring your art into Spine, save your project and run the ```Prepare-For-Spine``` script. You can find it under **File > Scripts > Prepare-For-Spine**. +3. Configure the export options as needed, then click the "Export" button. By default, the script will export a JSON file and a folder of PNG images to the same directory as your Aseprite project file. + * The default configuration is suitable for most users, so you can simply click the Export button to use the default settings. + +![alt text](Images/image.png) + +* Reset Config Button: Resets all options to their default values. + * This will also clear any cached settings, so the next time you open the options dialog it will be restored to the default values. +* Origin (X/Y): Sets the coordinate origin for the exported images. + * This coordinate origin will align with the coordinate origin in Spine, affecting the default position of the images when imported into Spine. + * The origin coordinates are normalized to the range [0,1], where (0,0) represents the bottom-left corner of the image and (1,1) represents the top-right corner. + * There are also quick preset buttons for common origin configurations (Center, Bottom-Center, Bottom-Left, Top-Left) that will automatically set the X and Y values accordingly. +* Round Coordinates to Integer: When enabled, the script will round all coordinate values to the nearest integer, dropping any decimal part. + * This may cause pixel misalignment. For example, if the origin is set to center and the image has odd pixel dimensions, the true center lies at the center of the middle pixel rather than on an edge. Forcing integer coordinates can therefore introduce a half-pixel offset. + * Pixel art usually requires perfect pixel alignment, so this option is not recommended unless you have a specific need. +* Output Path: Allows you to specify a custom output path for the exported JSON file. + * By default, it will be saved in the same directory as your Aseprite project file. + * You can type a path directly into the text field, or click the button below to open a file picker dialog. After selecting a location, the path is filled into the text field automatically. +* Ignore Group Visibility: When enabled, the script will ignore the visibility of groups during export. + * This only considers each layer's own visibility and ignores the visibility of its parent group. That means a layer can still be exported even if its group is hidden, as long as the layer itself is visible. +* Clear Old Images: When enabled, the script will automatically delete any previously exported images in the output directory before exporting new ones. + * This helps to prevent confusion and clutter from old files that are no longer relevant to the current export. +* Export Button: Starts the export process with the configured options. + * After export completes, click the [Open File Folder] button to open the directory containing the exported files. +* Cancel Button: Closes the options dialog without exporting. + +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). + +#### 「Spine Import」 + +1. Open Spine and create a new project. +2. Click the Spine Logo in the top left to open the file menu, and click **Import Data**. +3. Set up your Skeleton and start creating animations! + +### Known Issues + +#### v1.2 + +* Opening the exported file location currently relies on `os` library APIs and may cause a brief UI stall (a few seconds). +* Deleting old `images` files also relies on `os` library APIs and may cause a brief UI stall. + +#### v1.1 -### 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) * 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.2 + +* Enable Effective Group Visibility During Export + * Propagated group visibility downward during recursive traversal. + * Combined layer collection and effective-visibility recording into a single recursive pass to improve efficiency. + +* Added a new UI options panel + * Toggle for Ignore Group Visibility. + * Export path setting for the output JSON file. + +* Added updates to the UI options panel + * Toggle for Clear Old Images before export. + * Simplified output path selection workflow. + * Improved overall UI layout and spacing. + +* Added updates to the UI options panel + * Coordinate origin is now configurable (X/Y), with range support for [0,1]. + * Added a toggle to keep coordinate values as integers (drop decimal part). + * Added quick access to open the exported file location after export completion. + +* Added export workflow and coordinate UI improvements + * Added origin coordinate preset buttons for quick setup (Center, Bottom-Center, Bottom-Left, Top-Left). + * Added real-time clamping for origin coordinate inputs, limiting values to the [0,1] range. + * Added export completion dialog warnings that list any file paths that failed to write during export. + +* Added persistent UI configuration cache + * Added configuration caching for all export options, so settings are restored automatically on next launch. + * Added a Reset Config button to restore default values and clear cached settings. + #### v1.1 -- Changed to export images trimmed to the size of their non-transparent pixels. -- Hidden layers are not included in the json file for importing into Spine. +* Changed to export images trimmed to the size of their non-transparent pixels. +* Hidden layers are not included in the json file for importing into Spine. #### v1.0 diff --git a/aseprite/README_cn.md b/aseprite/README_cn.md new file mode 100644 index 0000000..c1e2622 --- /dev/null +++ b/aseprite/README_cn.md @@ -0,0 +1,112 @@ +### 更新 - 本脚本已收录到官方 Spine Scripts 仓库 + + + +___ + +# aseprite-to-spine +[English README](README.md) + +## 用于将 Aseprite 项目导入 Spine 的 Lua 脚本 + +## v1.2 + +### 安装 + +1. 打开 Aseprite +2. 进入 **File > Scripts > Open Scripts Folder** +3. 将附带的 ```Prepare-For-Spine.lua``` 文件拖入该目录 +4. 在 Aseprite 中点击 **File > Scripts > Rescan Scripts Folder** + +完成以上步骤后,你应该能在脚本列表中看到 "Prepare-For-Spine"。 + +### 使用说明 + +#### 「Aseprite 导出」 + +1. 像在 Photoshop 中那样创建你的 精灵。每个 "bone" 建议单独放在一个图层中。 +2. 当你准备将美术资源导入 Spine 时,先保存项目,然后运行 ```Prepare-For-Spine``` 脚本。你可以在 **File > Scripts > Prepare-For-Spine** 中找到它。 +3. 按需配置导出选项后,点击 "Export" 按钮。默认情况下,脚本会将 JSON 文件和 PNG 图片文件夹导出到 Aseprite 项目文件所在目录。 + * 默认配置 已经适合大多数用户的需求了,所以你可以 直接点击 Export 按钮使用默认配置进行导出。 + +![alt text](Images/image.png) + +* Reset Config 按钮:将所有选项 重置为 默认值。 + * 同时会 清除缓存设置,因此下次 打开选项弹窗时会 恢复默认配置。 +* Origin (X/Y):设置导出图像在 Spine 中使用的 坐标原点。 + * 这个 坐标原点 会与 Spine中的坐标原点 对齐,影响导入后图片在Spine中的 默认位置。 + * 原点坐标 被规范化到 [0,1] 区间,其中 (0,0) 表示图像 左下角,(1,1) 表示图像 右上角。 + * 提供了 常用原点的 预设按钮(Center、Bottom-Center、Bottom-Left、Top-Left),点击后会 自动设置对应的 X、Y 值。 +* Round Coordinates to Integer:启用后,脚本会将所有 坐标值取整,丢弃小数部分。 + * 这可能导致 像素不对齐。例如,将原点设为中心 且 图片像素尺寸为奇数时,几何中心会落在 中间像素中心 而不是边界上,强制整数坐标 可能带来 半像素偏移。 + * 像素风格 通常需要严格的 像素对齐,除非有特殊需求,否则不建议开启该选项。 +* Output Path:允许你为导出的 JSON 文件 指定自定义输出路径。 + * 默认会保存到 Aseprite 项目文件所在目录。 + * 你可以直接在 文本框中输入路径,或者点击 下方按钮 打开文件选择对话框。选择后,路径会 自动填入文本框。 +* Ignore Group Visibility:启用后,导出时将 忽略组可见性。 + * 仅根据 图层自身可见性判断,不考虑其 父组是否可见。这意味着即使 组被隐藏,只要图层本身可见,仍会被导出。 +* Clear Old Images:启用后,导出前会 自动删除 输出目录中旧的图片。 + * 这可以减少 旧文件残留 造成的 混淆和目录杂乱。 +* Export 按钮:使用当前配置 开始导出。 + * 导出完成后,可点击 [Open File Folder] 按钮直接 打开导出目录。 +* Cancel 按钮:关闭选项弹窗并 取消导出。 + +如果脚本 请求权限,请点击 "give full trust"(脚本仅需要文件写入权限以完成导出)。 + +#### 「Spine 导入」 + +1. 打开 Spine 并新建项目。 +2. 点击左上角 Spine 图标打开文件菜单,然后点击 **Import Data**。 +3. 配置 Skeleton 并开始制作动画。 + +### 已知问题 + +#### v1.2 + +* 打开 导出文件位置,目前依赖 `os` 库 API,可能导致短暂 UI 卡顿(几秒)。 +* 删除旧的 `images` 文件,同样依赖 `os` 库 API,也可能导致短暂 UI 卡顿。 + +#### v1.1 + +* 隐藏图层组 不会阻止 组内图层导出。每个图层都需要 单独设置显示/隐藏(组可见性会被忽略)。 +* 选项数量相比 Photoshop 脚本更少。后续可能会补充,但作者目前很少用到这些选项。 + +### 版本历史 + +#### v1.2 + +* 导出时启用组可见性的有效继承 + * 在递归遍历中将组可见性向下传递。 + * 将图层收集与有效可见性记录合并为一次递归遍历,以提升效率。 + +* 新增 UI 选项面板 + * 增加 Ignore Group Visibility 开关。 + * 增加 JSON 输出路径设置。 + +* UI 选项面板更新 + * 增加导出前清理旧图片(Clear Old Images)开关。 + * 简化输出路径选择流程。 + * 优化整体 UI 布局与间距。 + +* UI 选项面板更新 + * 支持配置坐标原点(X/Y),范围为 [0,1]。 + * 增加坐标取整开关(丢弃小数部分)。 + * 导出完成后可快速打开导出文件位置。 + +* 导出流程与坐标配置改进 + * 增加原点坐标预设按钮(Center、Bottom-Center、Bottom-Left、Top-Left)。 + * 增加原点坐标输入实时范围限制,自动约束到 [0,1]。 + * 导出完成弹窗支持列出写入失败的文件路径。 + +* 增加 UI 配置持久化缓存 + * 缓存所有导出选项,下次启动自动恢复。 + * 增加 Reset Config 按钮,可恢复默认值并清除缓存。 + +#### v1.1 + +* 导出的图片会自动裁剪到非透明像素区域大小。 +* 隐藏图层不会被写入用于导入 Spine 的 JSON 文件。 + +#### v1.0 + +初始发布 From e0e1687838333a0ed4f0a72cb7408e8b7bf08e03 Mon Sep 17 00:00:00 2001 From: Ale Date: Wed, 18 Mar 2026 14:52:45 +0900 Subject: [PATCH 08/25] [Aseprite] Update README v1.2 & refine content - Add Spine import configuration guide. - Restructure document hierarchy for better readability. --- aseprite/Images/{image.png => image-1.png} | Bin aseprite/Images/image-2.png | Bin 0 -> 20549 bytes aseprite/README.md | 28 +++++++++-- aseprite/README_cn.md | 56 ++++++++++++++------- 4 files changed, 60 insertions(+), 24 deletions(-) rename aseprite/Images/{image.png => image-1.png} (100%) create mode 100644 aseprite/Images/image-2.png diff --git a/aseprite/Images/image.png b/aseprite/Images/image-1.png similarity index 100% rename from aseprite/Images/image.png rename to aseprite/Images/image-1.png diff --git a/aseprite/Images/image-2.png b/aseprite/Images/image-2.png new file mode 100644 index 0000000000000000000000000000000000000000..1b7c0ff7d9735361c4a6defac9978710a3743343 GIT binary patch literal 20549 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 Scripts > Prepare-For-Spine**. 3. Configure the export options as needed, then click the "Export" button. By default, the script will export a JSON file and a folder of PNG images to the same directory as your Aseprite project file. * The default configuration is suitable for most users, so you can simply click the Export button to use the default settings. +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). -![alt text](Images/image.png) +![alt text](Images/image-1.png) * Reset Config Button: Resets all options to their default values. * This will also clear any cached settings, so the next time you open the options dialog it will be restored to the default values. @@ -51,14 +53,30 @@ After following these steps, the "Prepare-For-Spine" script should show up in th * After export completes, click the [Open File Folder] button to open the directory containing the exported files. * Cancel Button: Closes the options dialog without exporting. -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). - #### 「Spine Import」 1. Open Spine and create a new project. -2. Click the Spine Logo in the top left to open the file menu, and click **Import Data**. +2. Click the Spine Logo in the top left to open the file menu, and click **[Import Data]**. 3. Set up your Skeleton and start creating animations! +![alt text](Images/image-2.png) + +* Import: Import source. Here, use the default selected option: JSON or binary file. + * JSON or binary file: Import from a JSON file or a binary file. + * Folder: Import from a folder. +* File: Select the JSON file or folder to import. + * Click the folder icon button on the right to open the file picker dialog, then choose the JSON file to import or a folder that contains a JSON file. +* Scale: Import scale. The default value is 1.0, which means no scaling. + * Adjust this value as needed. For example, set it to 0.5 to import assets at half size, or set it to 2.0 to import assets at double size. +* New Project: If checked, a new project will be created during import. Otherwise, imported assets will be added to the currently open project. + * If you already created an empty new project, you do not need to check this option and can import directly. +* Create a new skeleton: If checked, a new skeleton will be created during import. + * If you already created an empty new project, you do not need to check this option and can import directly. +* Import into an existing skeleton: If checked, imported assets will be added to an existing skeleton. + * Replace existing attachments: If checked, attachments with the same name in the existing skeleton will be replaced during import. +* Import button: Start importing with the current configuration. +* Cancel button: Close the dialog and cancel the import. + ### Known Issues #### v1.2 diff --git a/aseprite/README_cn.md b/aseprite/README_cn.md index c1e2622..294ff0a 100644 --- a/aseprite/README_cn.md +++ b/aseprite/README_cn.md @@ -4,9 +4,10 @@ ___ -# aseprite-to-spine [English README](README.md) +# aseprite-to-spine + ## 用于将 Aseprite 项目导入 Spine 的 Lua 脚本 ## v1.2 @@ -28,8 +29,9 @@ ___ 2. 当你准备将美术资源导入 Spine 时,先保存项目,然后运行 ```Prepare-For-Spine``` 脚本。你可以在 **File > Scripts > Prepare-For-Spine** 中找到它。 3. 按需配置导出选项后,点击 "Export" 按钮。默认情况下,脚本会将 JSON 文件和 PNG 图片文件夹导出到 Aseprite 项目文件所在目录。 * 默认配置 已经适合大多数用户的需求了,所以你可以 直接点击 Export 按钮使用默认配置进行导出。 +4. 如果脚本 请求权限,请点击 "give full trust"(脚本仅需要文件写入权限以完成导出)。 -![alt text](Images/image.png) +![alt text](Images/image-1.png) * Reset Config 按钮:将所有选项 重置为 默认值。 * 同时会 清除缓存设置,因此下次 打开选项弹窗时会 恢复默认配置。 @@ -51,14 +53,30 @@ ___ * 导出完成后,可点击 [Open File Folder] 按钮直接 打开导出目录。 * Cancel 按钮:关闭选项弹窗并 取消导出。 -如果脚本 请求权限,请点击 "give full trust"(脚本仅需要文件写入权限以完成导出)。 - #### 「Spine 导入」 1. 打开 Spine 并新建项目。 -2. 点击左上角 Spine 图标打开文件菜单,然后点击 **Import Data**。 +2. 点击左上角 Spine 图标打开文件菜单,然后点击 **[Import Data]**。 3. 配置 Skeleton 并开始制作动画。 +![alt text](Images/image-2.png) + +* Import:导入来源。这里使用 默认选择的 JSON or binary file。 + * JSON or binary file:从 JSON 或二进制文件导入。 + * Folder:从文件夹导入。 +* File:选择要导入的 JSON 文件或文件夹。 + * 点击右侧的 “文件夹”图标按钮,可以打开 文件选择对话框,选择要导入的 JSON 文件或包含 JSON 文件的文件夹。 +* Scale:导入时的缩放比例。默认值为 1.0,表示不缩放。 + * 可以根据需要 调整该值,例如设置为 0.5 将导入资源 缩小一半,设置为 2.0 将导入资源 放大两倍。 +* New Project:如果选中,导入时会 创建一个新项目。否则,导入的资源会被添加到 当前打开的项目中。 + * 如果已经创建了 空的新项目,则不需要选中该选项,直接导入即可。 +* Create a new skeleton:如果选中,导入时会 创建一个新的骨架。 + * 如果已经创建了 空的新项目,则不需要选中该选项,直接导入即可。 +* Import into an existing skeleton:如果选中,导入的资源会被添加到 现有骨架中。 + * Replace existing attachments:如果选中,导入时会 替换现有骨架中 同名的附件。 +* Import 按钮:使用当前配置 开始导入。 +* Cancel 按钮:关闭对话框并 取消导入。 + ### 已知问题 #### v1.2 @@ -76,36 +94,36 @@ ___ #### v1.2 * 导出时启用组可见性的有效继承 - * 在递归遍历中将组可见性向下传递。 - * 将图层收集与有效可见性记录合并为一次递归遍历,以提升效率。 + * 在递归遍历中 将组可见性 向下传递。 + * 将 图层收集 与 有效可见性记录 合并为一次递归遍历,以提升效率。 * 新增 UI 选项面板 * 增加 Ignore Group Visibility 开关。 * 增加 JSON 输出路径设置。 * UI 选项面板更新 - * 增加导出前清理旧图片(Clear Old Images)开关。 - * 简化输出路径选择流程。 + * 增加导出前 清理旧图片(Clear Old Images)开关。 + * 简化 输出路径选择流程。 * 优化整体 UI 布局与间距。 * UI 选项面板更新 - * 支持配置坐标原点(X/Y),范围为 [0,1]。 - * 增加坐标取整开关(丢弃小数部分)。 - * 导出完成后可快速打开导出文件位置。 + * 支持配置 坐标原点(X/Y),范围为 [0,1]。 + * 增加 坐标取整 开关(丢弃小数部分)。 + * 导出完成后 可快速打开 导出文件位置。 * 导出流程与坐标配置改进 - * 增加原点坐标预设按钮(Center、Bottom-Center、Bottom-Left、Top-Left)。 - * 增加原点坐标输入实时范围限制,自动约束到 [0,1]。 - * 导出完成弹窗支持列出写入失败的文件路径。 + * 增加 原点坐标预设按钮(Center、Bottom-Center、Bottom-Left、Top-Left)。 + * 增加 原点坐标输入 实时范围限制,自动约束到 [0,1]。 + * 导出完成弹窗 支持列出 写入失败的文件路径。 * 增加 UI 配置持久化缓存 - * 缓存所有导出选项,下次启动自动恢复。 - * 增加 Reset Config 按钮,可恢复默认值并清除缓存。 + * 缓存所有 导出选项,下次启动 自动恢复。 + * 增加 Reset Config 按钮,可恢复默认值 并清除缓存。 #### v1.1 -* 导出的图片会自动裁剪到非透明像素区域大小。 -* 隐藏图层不会被写入用于导入 Spine 的 JSON 文件。 +* 导出的图片会 自动裁剪 到非透明像素区域大小。 +* 隐藏图层 不会被写入用于导入 Spine 的 JSON 文件。 #### v1.0 From ef79814f36316c21c211010334e568cadad84864 Mon Sep 17 00:00:00 2001 From: Ale Date: Wed, 18 Mar 2026 15:29:01 +0900 Subject: [PATCH 09/25] [Aseprite] Cleanup whitespace and format code --- aseprite/Prepare-For-Spine.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 9b03d40..b558cfb 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -22,11 +22,11 @@ function flattenWithEffectiveVisibility(parent, outLayers, outVis, inheritedVisi else effectiveVisible = inheritedVisible and layer.isVisible end - + -- Append the layer and its effective visibility to the output arrays outLayers[#outLayers + 1] = layer outVis[#outVis + 1] = effectiveVisible - + -- If this layer is a group, recursively flatten its children, passing down the effective visibilityStates if layer.isGroup then local nextInherited = ignoreGroupVisibility and true or effectiveVisible @@ -120,13 +120,13 @@ originX, originY: the user-defined origin point for the exported Spine skeleton, roundCoordinatesToInteger: if true, rounds the attachment coordinates to the nearest integer instead of keeping decimals (not recommended for pixel art) ]] function captureLayers( - layers, - sprite, - effectiveVisibilities, - outputPath, - clearOldImages, - originX, - originY, + layers, + sprite, + effectiveVisibilities, + outputPath, + clearOldImages, + originX, + originY, roundCoordinatesToInteger) -- Default output path to the sprite-name json in the sprite's directory. if (outputPath == nil or outputPath == "") then @@ -365,7 +365,7 @@ function showExportOptionsDialog() clampOriginField("originY", 0) end }) - + -- button: Presets for common origin settings (center, bottom-center, bottom-left, top-left) local function setOriginPreset(x, y) optionsDialog:modify({ id = "originX", text = string.format("%.3f", x) }) @@ -406,7 +406,7 @@ function showExportOptionsDialog() selected = cachedOptions.roundCoordinatesToInteger }) optionsDialog:separator({}) ---#endregion + --#endregion --#region Output Path Settings -- entry: Output json path @@ -450,7 +450,7 @@ function showExportOptionsDialog() }) optionsDialog:separator({}) --#endregion - + --#region Execution Buttons -- button: Confirm export local confirmed = false From 4a03d7897b96be288f6fc143f752383602bb400c Mon Sep 17 00:00:00 2001 From: Ale Date: Wed, 18 Mar 2026 18:55:11 +0900 Subject: [PATCH 10/25] [Aseprite] Update README v1.2 - Added a Known Issues regarding potential Draw Order issues in Spine when importing new layers from Aseprite. --- aseprite/README.md | 2 ++ aseprite/README_cn.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/aseprite/README.md b/aseprite/README.md index 32e6181..a2f70d2 100644 --- a/aseprite/README.md +++ b/aseprite/README.md @@ -74,6 +74,7 @@ After following these steps, the "Prepare-For-Spine" script should show up in th * If you already created an empty new project, you do not need to check this option and can import directly. * Import into an existing skeleton: If checked, imported assets will be added to an existing skeleton. * Replace existing attachments: If checked, attachments with the same name in the existing skeleton will be replaced during import. + * New layers will generate new attachments and be added to the existing skeleton, but the draw order may be incorrect and needs to be manually adjusted in Spine. * Import button: Start importing with the current configuration. * Cancel button: Close the dialog and cancel the import. @@ -83,6 +84,7 @@ After following these steps, the "Prepare-For-Spine" script should show up in th * Opening the exported file location currently relies on `os` library APIs and may cause a brief UI stall (a few seconds). * Deleting old `images` files also relies on `os` library APIs and may cause a brief UI stall. +* New layers added in Aseprite may have incorrect draw order when imported into an existing Spine skeleton, and need to be adjusted manually in Spine. #### v1.1 diff --git a/aseprite/README_cn.md b/aseprite/README_cn.md index 294ff0a..751b4b7 100644 --- a/aseprite/README_cn.md +++ b/aseprite/README_cn.md @@ -74,6 +74,7 @@ ___ * 如果已经创建了 空的新项目,则不需要选中该选项,直接导入即可。 * Import into an existing skeleton:如果选中,导入的资源会被添加到 现有骨架中。 * Replace existing attachments:如果选中,导入时会 替换现有骨架中 同名的附件。 + * 新增的图层 会生成 新的附件 并添加到 现有的骨架中,但是 绘制顺序 可能会出现问题,需要在 Spine 中手动调整。 * Import 按钮:使用当前配置 开始导入。 * Cancel 按钮:关闭对话框并 取消导入。 @@ -83,6 +84,7 @@ ___ * 打开 导出文件位置,目前依赖 `os` 库 API,可能导致短暂 UI 卡顿(几秒)。 * 删除旧的 `images` 文件,同样依赖 `os` 库 API,也可能导致短暂 UI 卡顿。 +* Aseprite 中新增的图层,在导入到 Spine 的现有骨架时,可能会出现 绘制顺序不正确 的问题,需要在 Spine 中手动调整。 #### v1.1 From 3fed253ce67a18d574d763ab0c1c165a75d20379 Mon Sep 17 00:00:00 2001 From: Ale Date: Thu, 19 Mar 2026 00:49:59 +0900 Subject: [PATCH 11/25] [Aseprite] Add coordinate modes and refine layer visibility options - Added Normalized [0,1] and Pixel modes for origin coordinates. - Added Sliders for Origin (X, Y) to allow more intuitive control. - Added "Ignore Hidden Layers" toggle for more flexible exports. - Removed redundant "Use layer visibility only" option. --- aseprite/Prepare-For-Spine.lua | 514 ++++++++++++++++++++++++++++----- 1 file changed, 448 insertions(+), 66 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index b558cfb..4357054 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -6,31 +6,30 @@ https://github.com/jordanbleu/aseprite-to-spine -----------------------------------------------[[ Functions ]]----------------------------------------------- --[[ -Flattens the layers of a sprite while allowing optional ignore of parent group visibility. +Flattens the layers of a sprite and computes each layer's export visibility. parent: The sprite or parent layer group outLayers: The array to append the flattened layers outVis: The array to append the effective visibility of each layer (true / false) -inheritedVisible: The visibility inherited from parent groups (true / false) -ignoreGroupVisibility: If true, visibility only depends on the layer's own isVisible value +groupIsVisible: The visibility inherited from parent groups (true / false) +ignoreHiddenLayers: If true, hidden layers and layers under hidden groups are excluded ]] -function flattenWithEffectiveVisibility(parent, outLayers, outVis, inheritedVisible, ignoreGroupVisibility) +function flattenWithEffectiveVisibility(parent, outLayers, outVis, groupIsVisible, ignoreHiddenLayers) for _, layer in ipairs(parent.layers) do -- Determine the effective visibility of the layer based on its own visibility and the inherited visibility from parent groups local effectiveVisible - if (ignoreGroupVisibility) then - effectiveVisible = layer.isVisible + if (ignoreHiddenLayers) then + effectiveVisible = groupIsVisible and layer.isVisible else - effectiveVisible = inheritedVisible and layer.isVisible + effectiveVisible = true end - + -- Append the layer and its effective visibility to the output arrays outLayers[#outLayers + 1] = layer outVis[#outVis + 1] = effectiveVisible - - -- If this layer is a group, recursively flatten its children, passing down the effective visibilityStates + + -- If this layer is a group, recursively flatten its children, passing down the effective visibility if layer.isGroup then - local nextInherited = ignoreGroupVisibility and true or effectiveVisible - flattenWithEffectiveVisibility(layer, outLayers, outVis, nextInherited, ignoreGroupVisibility) + flattenWithEffectiveVisibility(layer, outLayers, outVis, effectiveVisible, ignoreHiddenLayers) end end end @@ -120,13 +119,13 @@ originX, originY: the user-defined origin point for the exported Spine skeleton, roundCoordinatesToInteger: if true, rounds the attachment coordinates to the nearest integer instead of keeping decimals (not recommended for pixel art) ]] function captureLayers( - layers, - sprite, - effectiveVisibilities, - outputPath, - clearOldImages, - originX, - originY, + layers, + sprite, + effectiveVisibilities, + outputPath, + clearOldImages, + originX, + originY, roundCoordinatesToInteger) -- Default output path to the sprite-name json in the sprite's directory. if (outputPath == nil or outputPath == "") then @@ -296,25 +295,28 @@ Shows the export options dialog and returns the selected options. ]] function showExportOptionsDialog() -- Create a dialog to show export optionsDialog - local optionsDialog = Dialog({ title = "Export To Spine" }) + local optionsDialog = Dialog({ title = "Export To Spine v1.3" }) -- Load cached options or use defaults if no cache exists local activeSprite = app.activeSprite + local spriteWidth, spriteHeight = getActiveSpriteSize() local spriteOutputDir = app.fs.filePath(activeSprite.filename) local spriteOutputName = app.fs.fileTitle(activeSprite.filename) local defaultOutputPath = spriteOutputDir .. app.fs.pathSeparator .. spriteOutputName .. ".json" local cachedOptions, configPath = loadCachedOptions(defaultOutputPath) + CURRENT_ORIGIN_MODE = cachedOptions.originMode --#region Other Buttons -- button: Resets all options to their default values optionsDialog:button({ text = "Reset Config", onclick = function() + setOriginMode(optionsDialog, ORIGIN_MODE.NORMALIZED) optionsDialog:modify({ id = "originX", text = string.format("%.3f", 0.5) }) optionsDialog:modify({ id = "originY", text = string.format("%.3f", 0) }) optionsDialog:modify({ id = "roundCoordinatesToInteger", selected = false }) optionsDialog:modify({ id = "outputPath", text = defaultOutputPath }) - optionsDialog:modify({ id = "ignoreGroupVisibility", selected = false }) + optionsDialog:modify({ id = "ignoreHiddenLayers", selected = true }) optionsDialog:modify({ id = "clearOldImages", selected = false }) end }) @@ -324,37 +326,40 @@ function showExportOptionsDialog() --#region Coordinate Settings - -- function: Clamps a number to the range [0,1]. - local function clampTo01(value) - if (value < 0) then - return 0 - elseif (value > 1) then - return 1 - end - return value - end - -- function: Clamps the input field for originX and originY to the range [0,1]. - local function clampOriginField(fieldId, fallback) - local parsed = tonumber(optionsDialog.data[fieldId]) - if (parsed == nil) then - parsed = fallback - end - optionsDialog:modify({ id = fieldId, text = string.format("%.3f", clampTo01(parsed)) }) - end - optionsDialog:label({ id = "coordinateSettings", label = "Coordinate Settings", text = "Set which position is used as the Spine origin (0,0). Range: [0,1]." }) - -- number: Coordinate origin X and Y (0-1). + -- radio: Option to choose between normalized coordinates (0-1) or pixel-based coordinates + optionsDialog:radio({ + id = "originModeNormalized", + label = "Origin Mode", + text = ORIGIN_MODE.NORMALIZED, + selected = cachedOptions.originMode == ORIGIN_MODE.NORMALIZED, + onclick = function() + setOriginMode(optionsDialog, ORIGIN_MODE.NORMALIZED) + applyOriginMode(optionsDialog) + end + }) + optionsDialog:radio({ + id = "originModePixel", + text = ORIGIN_MODE.PIXEL, + selected = cachedOptions.originMode == ORIGIN_MODE.PIXEL, + onclick = function() + setOriginMode(optionsDialog, ORIGIN_MODE.PIXEL) + applyOriginMode(optionsDialog) + end + }) + setOriginMode(optionsDialog, cachedOptions.originMode) + -- number + slider: Coordinate origin X and Y. optionsDialog:number({ id = "originX", label = "Origin (X,Y)", text = string.format("%.3f", cachedOptions.originX), decimals = 3, onchange = function() - clampOriginField("originX", 0.5) + clampOriginXyFieldValue(optionsDialog) end }) :number({ @@ -362,38 +367,53 @@ function showExportOptionsDialog() text = string.format("%.3f", cachedOptions.originY), decimals = 3, onchange = function() - clampOriginField("originY", 0) + clampOriginXyFieldValue(optionsDialog) + end + }) + :slider({ + id = "originXSlider", + min = 0, + max = ORIGIN_SLIDER_STEPS, + value = 0, + onchange = function() + applyOriginSliderChange(optionsDialog, "x") + end + }) + :slider({ + id = "originYSlider", + min = 0, + max = ORIGIN_SLIDER_STEPS, + value = 0, + onchange = function() + applyOriginSliderChange(optionsDialog, "y") end }) + applyOriginMode(optionsDialog) -- button: Presets for common origin settings (center, bottom-center, bottom-left, top-left) - local function setOriginPreset(x, y) - optionsDialog:modify({ id = "originX", text = string.format("%.3f", x) }) - optionsDialog:modify({ id = "originY", text = string.format("%.3f", y) }) - end optionsDialog:newrow() optionsDialog:button({ text = "Center", onclick = function() - setOriginPreset(0.5, 0.5) + setOriginPreset(optionsDialog, "center") end }) optionsDialog:button({ text = "Bottom-Center", onclick = function() - setOriginPreset(0.5, 0) + setOriginPreset(optionsDialog, "bottom-center") end }) optionsDialog:button({ text = "Bottom-Left", onclick = function() - setOriginPreset(0, 0) + setOriginPreset(optionsDialog, "bottom-left") end }) optionsDialog:button({ text = "Top-Left", onclick = function() - setOriginPreset(0, 1) + setOriginPreset(optionsDialog, "top-left") end }) optionsDialog:newrow() @@ -406,7 +426,7 @@ function showExportOptionsDialog() selected = cachedOptions.roundCoordinatesToInteger }) optionsDialog:separator({}) - --#endregion +--#endregion --#region Output Path Settings -- entry: Output json path @@ -433,12 +453,12 @@ function showExportOptionsDialog() --#endregion --#region Other Settings - -- check: Option to ignore group visibility when determining layer visibilityStates + -- check: Option to skip exporting hidden layers (including layers under hidden groups) optionsDialog:check({ - id = "ignoreGroupVisibility", - label = "Ignore Group Visibility", - text = "Use layer visibility only.", - selected = cachedOptions.ignoreGroupVisibility + id = "ignoreHiddenLayers", + label = "Ignore Hidden Layers", + text = "Hidden layers and layers under hidden groups will not be output.", + selected = cachedOptions.ignoreHiddenLayers }) -- check: Option to clear old images in the output images directory before export @@ -450,7 +470,7 @@ function showExportOptionsDialog() }) optionsDialog:separator({}) --#endregion - + --#region Execution Buttons -- button: Confirm export local confirmed = false @@ -476,17 +496,26 @@ function showExportOptionsDialog() optionsDialog:show({ wait = true}) --#region options Data Extraction - -- Get the selected options from the dialog + -- Get the selected options local options = optionsDialog.data + options.originMode = getOriginMode() -- Fallback to default path when input is empty. if (options.outputPath == nil or options.outputPath == "") then options.outputPath = defaultOutputPath end -- Parse originX and originY as numbers, and fallback to defaults if parsing fails or values are out of range + if (options.originMode ~= ORIGIN_MODE.PIXEL and options.originMode ~= ORIGIN_MODE.NORMALIZED) then + options.originMode = ORIGIN_MODE.NORMALIZED + end local parsedOriginX = tonumber(options.originX) local parsedOriginY = tonumber(options.originY) - options.originX = clampTo01(parsedOriginX or 0.5) - options.originY = clampTo01(parsedOriginY or 0) + if (options.originMode == ORIGIN_MODE.PIXEL) then + options.originX = clampValue(parsedOriginX or (spriteWidth * 0.5), 0, spriteWidth) + options.originY = clampValue(parsedOriginY or 0, 0, spriteHeight) + elseif (options.originMode == ORIGIN_MODE.NORMALIZED) then + options.originX = clampValue(parsedOriginX or 0.5, 0, 1) + options.originY = clampValue(parsedOriginY or 0, 0, 1) + end -- Save the options to cache so they will be remembered next time the dialog is opened. saveCachedOptions(configPath, options) @@ -554,6 +583,348 @@ function showExportCompletedDialog(jsonFileName, failedPaths) completedDialog:show({ wait = true }) end +--#region Coordinates Origin Mode Functions +ORIGIN_MODE = { + NORMALIZED = "Normalized", -- Normalized origin coordinates in the range [0,1], where (0,0) is the bottom-left. + PIXEL = "Pixel", -- Pixel-based origin coordinates, where (0,0) is the bottom-left of the sprite and values are in pixels. +} +CURRENT_ORIGIN_MODE = ORIGIN_MODE.NORMALIZED +ORIGIN_SLIDER_STEPS = 100 +ORIGIN_SLIDER_IS_SYNCING = false + +--[[ +Sets the selected origin mode radio button in the options dialog based on the given mode. +optionsDialog: The export options dialog instance +mode: The origin mode to select (ORIGIN_MODE.PIXEL or ORIGIN_MODE.NORMALIZED) +]] +function setOriginMode(optionsDialog, mode) + local currentMode = CURRENT_ORIGIN_MODE + + if (currentMode ~= mode) then + convertOriginCoordinatesByMode(optionsDialog, mode) + optionsDialog:modify({ id = "originModeNormalized", selected = mode == ORIGIN_MODE.NORMALIZED }) + optionsDialog:modify({ id = "originModePixel", selected = mode == ORIGIN_MODE.PIXEL }) + CURRENT_ORIGIN_MODE = mode + else + optionsDialog:modify({ id = "originModeNormalized", selected = mode == ORIGIN_MODE.NORMALIZED }) + optionsDialog:modify({ id = "originModePixel", selected = mode == ORIGIN_MODE.PIXEL }) + end +end + +--[[ +Gets the currently selected origin mode from the options dialog. +optionsDialog: The export options dialog instance +]] +function getOriginMode() + return CURRENT_ORIGIN_MODE +end + +--[[ +Applies the selected origin mode to the originX and originY fields. +optionsDialog: The export options dialog instance +]] +function applyOriginMode(optionsDialog) + local spriteWidth, spriteHeight = getActiveSpriteSize() + local mode = getOriginMode() + + local currentX = tonumber(optionsDialog.data.originX) + local currentY = tonumber(optionsDialog.data.originY) + if (mode == ORIGIN_MODE.PIXEL) then + if (currentX == nil) then + currentX = spriteWidth * 0.5 + end + if (currentY == nil) then + currentY = 0 + end + else + if (currentX == nil) then + currentX = 0.5 + end + if (currentY == nil) then + currentY = 0 + end + end + + optionsDialog:modify({ id = "originX", text = tostring(currentX) }) + optionsDialog:modify({ id = "originY", text = tostring(currentY) }) + clampOriginXyFieldValue(optionsDialog) + + -- Update the coordinate settings label to show the valid input range for the selected origin mode + if (mode == ORIGIN_MODE.PIXEL) then + optionsDialog:modify({ + id = "coordinateSettings", + text = string.format("Set Spine origin(0,0) in pixels. Range X:[0,%.0f], Y:[0,%.0f]", spriteWidth, spriteHeight) + }) + elseif (mode == ORIGIN_MODE.NORMALIZED) then + optionsDialog:modify({ + id = "coordinateSettings", + text = "Set Spine origin(0,0) in normalized coordinates. Range: [0,1]" + }) + end +end + +--[[ +Converts current origin coordinates in the options dialog between normalized and pixel modes. +optionsDialog: The export options dialog instance +toMode: The target mode +]] +function convertOriginCoordinatesByMode(optionsDialog, toMode) + local spriteWidth, spriteHeight = getActiveSpriteSize() + if (spriteWidth == nil or spriteWidth <= 0) then + spriteWidth = 1 + end + if (spriteHeight == nil or spriteHeight <= 0) then + spriteHeight = 1 + end + + local originX = tonumber(optionsDialog.data.originX) + local originY = tonumber(optionsDialog.data.originY) + + if (originX == nil) then + if (toMode == ORIGIN_MODE.PIXEL) then + originX = 0.5 + else + originX = spriteWidth * 0.5 + end + end + if (originY == nil) then + originY = 0 + end + + local convertedX + local convertedY + if (toMode == ORIGIN_MODE.PIXEL) then + convertedX = originX * spriteWidth + convertedY = originY * spriteHeight + else + convertedX = originX / spriteWidth + convertedY = originY / spriteHeight + end + + optionsDialog:modify({ id = "originX", text = tostring(convertedX) }) + optionsDialog:modify({ id = "originY", text = tostring(convertedY) }) +end + +--[[ +Clamps the originX and originY fields in the options dialog to valid ranges based on the selected origin mode. +optionsDialog: The export options dialog instance +]] +function clampOriginXyFieldValue(optionsDialog) + -- Determine the valid range for originX and originY based on the selected origin mode + local spriteWidth, spriteHeight = getActiveSpriteSize() + local mode = getOriginMode() + local maxX = mode == ORIGIN_MODE.PIXEL and spriteWidth or 1 + local maxY = mode == ORIGIN_MODE.PIXEL and spriteHeight or 1 + + -- Parse the current values of originX and originY, and fallback to defaults if parsing fails + local parsedX = tonumber(optionsDialog.data.originX) + local parsedY = tonumber(optionsDialog.data.originY) + if (parsedX == nil) then + parsedX = mode == ORIGIN_MODE.PIXEL and (spriteWidth * 0.5) or 0.5 + end + if (parsedY == nil) then + parsedY = 0 + end + + local clampedX = clampValue(parsedX, 0, maxX) + local clampedY = clampValue(parsedY, 0, maxY) + optionsDialog:modify({ id = "originX", text = string.format("%.3f", clampedX) }) + optionsDialog:modify({ id = "originY", text = string.format("%.3f", clampedY) }) + syncOriginSlidersFromFields(optionsDialog) +end + +--[[ +Sets the originX and originY fields in the options dialog to preset values based on common Spine origin settings. +optionsDialog: The export options dialog instance +presetName: The name of the preset to apply ("center", "bottom-center", "top-left", "bottom-left") +]] +function setOriginPreset(optionsDialog, presetName) + local spriteWidth, spriteHeight = getActiveSpriteSize() + local mode = getOriginMode() + + local x = 0 + local y = 0 + if (presetName == "center") then + if (mode == ORIGIN_MODE.PIXEL) then + x = spriteWidth * 0.5 + y = spriteHeight * 0.5 + else + x = 0.5 + y = 0.5 + end + elseif (presetName == "bottom-center") then + if (mode == ORIGIN_MODE.PIXEL) then + x = spriteWidth * 0.5 + y = 0 + else + x = 0.5 + y = 0 + end + elseif (presetName == "top-left") then + if (mode == ORIGIN_MODE.PIXEL) then + x = 0 + y = spriteHeight + else + x = 0 + y = 1 + end + elseif (presetName == "bottom-left") then + if (mode == ORIGIN_MODE.PIXEL) then + x = 0 + y = 0 + else + x = 0 + y = 0 + end + end + + optionsDialog:modify({ id = "originX", text = string.format("%.3f", x) }) + optionsDialog:modify({ id = "originY", text = string.format("%.3f", y) }) + clampOriginXyFieldValue(optionsDialog) +end + +--[[ +Converts origin coordinates to normalized values based on the selected origin mode. +mode: The origin mode (ORIGIN_MODE.PIXEL or ORIGIN_MODE.NORMALIZED) +originX: The X coordinate of the origin +originY: The Y coordinate of the origin +]] +function getNormalizeOriginCoordinates(mode, originX, originY) + -- If the mode is PIXEL, convert the pixel-based originX and originY to normalized coordinates. + if (mode == ORIGIN_MODE.PIXEL) then + local spriteWidth, spriteHeight = getActiveSpriteSize() + if (spriteWidth == nil or spriteWidth <= 0) then + spriteWidth = 1 + end + if (spriteHeight == nil or spriteHeight <= 0) then + spriteHeight = 1 + end + + local normalizedX = clampValue((originX or 0) / spriteWidth, 0, 1) + local normalizedY = clampValue((originY or 0) / spriteHeight, 0, 1) + return normalizedX, normalizedY + end + + return clampValue(originX or 0.5, 0, 1), clampValue(originY or 0, 0, 1) +end + +--[[ +Clamps a value between a minimum and maximum range. +value: The value to clamp +minValue: The minimum allowed value +maxValue: The maximum allowed value +]] +function clampValue(value, minValue, maxValue) + if (value < minValue) then + return minValue + elseif (value > maxValue) then + return maxValue + end + return value +end + +-- Returns the width and height of the active sprite, or 0 if no active sprite is found. +function getActiveSpriteSize() + local activeSprite = app.activeSprite + local spriteWidth = 0 + local spriteHeight = 0 + if (activeSprite ~= nil and activeSprite.bounds ~= nil) then + spriteWidth = activeSprite.bounds.width + spriteHeight = activeSprite.bounds.height + end + return spriteWidth, spriteHeight +end + +--#region Origin Coordinate Sliders Functions + +-- Converts a coordinate value into slider step value based on current mode range. +function toOriginSliderValue(value, maxValue) + if (maxValue == nil or maxValue <= 0) then + maxValue = 1 + end + local normalized = clampValue((value or 0) / maxValue, 0, 1) + return math.floor(normalized * ORIGIN_SLIDER_STEPS + 0.5) +end + +-- Converts a slider step value back into coordinate value based on current mode range. +function fromOriginSliderValue(sliderValue, maxValue) + if (maxValue == nil or maxValue <= 0) then + maxValue = 1 + end + local step = clampValue(sliderValue or 0, 0, ORIGIN_SLIDER_STEPS) + local normalized = step / ORIGIN_SLIDER_STEPS + return normalized * maxValue +end + +-- Syncs the slider positions from current originX/originY input values. +function syncOriginSlidersFromFields(optionsDialog) + if (ORIGIN_SLIDER_IS_SYNCING) then + return + end + + local spriteWidth, spriteHeight = getActiveSpriteSize() + local mode = getOriginMode() + local maxX = mode == ORIGIN_MODE.PIXEL and spriteWidth or 1 + local maxY = mode == ORIGIN_MODE.PIXEL and spriteHeight or 1 + + if (maxX <= 0) then + maxX = 1 + end + if (maxY <= 0) then + maxY = 1 + end + + local x = tonumber(optionsDialog.data.originX) + local y = tonumber(optionsDialog.data.originY) + if (x == nil) then + x = mode == ORIGIN_MODE.PIXEL and (spriteWidth * 0.5) or 0.5 + end + if (y == nil) then + y = 0 + end + + ORIGIN_SLIDER_IS_SYNCING = true + optionsDialog:modify({ id = "originXSlider", value = toOriginSliderValue(x, maxX) }) + optionsDialog:modify({ id = "originYSlider", value = toOriginSliderValue(y, maxY) }) + ORIGIN_SLIDER_IS_SYNCING = false +end + +-- Applies slider movement back to originX/originY inputs. +function applyOriginSliderChange(optionsDialog, axis) + if (ORIGIN_SLIDER_IS_SYNCING) then + return + end + + local spriteWidth, spriteHeight = getActiveSpriteSize() + local mode = getOriginMode() + local maxX = mode == ORIGIN_MODE.PIXEL and spriteWidth or 1 + local maxY = mode == ORIGIN_MODE.PIXEL and spriteHeight or 1 + + if (maxX <= 0) then + maxX = 1 + end + if (maxY <= 0) then + maxY = 1 + end + + local sliderX = tonumber(optionsDialog.data.originXSlider) or 0 + local sliderY = tonumber(optionsDialog.data.originYSlider) or 0 + + ORIGIN_SLIDER_IS_SYNCING = true + if (axis == "x") then + local x = fromOriginSliderValue(sliderX, maxX) + optionsDialog:modify({ id = "originX", text = string.format("%.3f", x) }) + elseif (axis == "y") then + local y = fromOriginSliderValue(sliderY, maxY) + optionsDialog:modify({ id = "originY", text = string.format("%.3f", y) }) + end + ORIGIN_SLIDER_IS_SYNCING = false + + clampOriginXyFieldValue(optionsDialog) +end +--#endregion +--#endregion + --#region Config Caching Functions --[[ @@ -578,9 +949,10 @@ function loadCachedOptions(defaultOutputPath) local cached = { originX = 0.5, originY = 0, + originMode = ORIGIN_MODE.NORMALIZED, roundCoordinatesToInteger = false, outputPath = defaultOutputPath, - ignoreGroupVisibility = false, + ignoreHiddenLayers = true, clearOldImages = false } -- Create a config directory under the user's Aseprite config path, and define the config file path @@ -603,8 +975,11 @@ function loadCachedOptions(defaultOutputPath) cached.originX = tonumber(raw.originX) or cached.originX cached.originY = tonumber(raw.originY) or cached.originY + if (raw.originMode == ORIGIN_MODE.PIXEL or raw.originMode == ORIGIN_MODE.NORMALIZED) then + cached.originMode = raw.originMode + end cached.roundCoordinatesToInteger = parseBool(raw.roundCoordinatesToInteger, cached.roundCoordinatesToInteger) - cached.ignoreGroupVisibility = parseBool(raw.ignoreGroupVisibility, cached.ignoreGroupVisibility) + cached.ignoreHiddenLayers = parseBool(raw.ignoreHiddenLayers, cached.ignoreHiddenLayers) cached.clearOldImages = parseBool(raw.clearOldImages, cached.clearOldImages) if (raw.outputPath ~= nil and raw.outputPath ~= "") then cached.outputPath = raw.outputPath @@ -626,9 +1001,10 @@ function saveCachedOptions(configPath, options) configFile:write("originX=" .. string.format("%.3f", options.originX) .. "\n") configFile:write("originY=" .. string.format("%.3f", options.originY) .. "\n") + configFile:write("originMode=" .. (options.originMode or ORIGIN_MODE.NORMALIZED) .. "\n") configFile:write("roundCoordinatesToInteger=" .. tostring(options.roundCoordinatesToInteger == true) .. "\n") configFile:write("outputPath=" .. (options.outputPath or "") .. "\n") - configFile:write("ignoreGroupVisibility=" .. tostring(options.ignoreGroupVisibility == true) .. "\n") + configFile:write("ignoreHiddenLayers=" .. tostring(options.ignoreHiddenLayers == true) .. "\n") configFile:write("clearOldImages=" .. tostring(options.clearOldImages == true) .. "\n") configFile:close() end @@ -655,7 +1031,7 @@ end local flattenedLayers = {} -- This will be the flattened view of the sprite layers, ignoring groups local effectiveVisibilities = {} -- This will be the effective visibility of each layer (true / false) -flattenWithEffectiveVisibility(activeSprite, flattenedLayers, effectiveVisibilities, true, options.ignoreGroupVisibility) +flattenWithEffectiveVisibility(activeSprite, flattenedLayers, effectiveVisibilities, true, options.ignoreHiddenLayers) if (containsDuplicates(flattenedLayers, effectiveVisibilities)) then return @@ -664,6 +1040,12 @@ end -- Get an array containing each layer index and whether it is currently visible local visibilities = captureVisibilityStates(flattenedLayers) +-- Calculate the normalized origin coordinates (range 0-1) based on the user's selected origin mode and input values +local normalizedOriginX, normalizedOriginY = getNormalizeOriginCoordinates( + options.originMode, + options.originX, + options.originY +) -- Saves each sprite layer as a separate .png under the 'images' subdirectory captureLayers( flattenedLayers, @@ -671,8 +1053,8 @@ captureLayers( effectiveVisibilities, options.outputPath, options.clearOldImages, - options.originX, - options.originY, + normalizedOriginX, + normalizedOriginY, options.roundCoordinatesToInteger ) From b3d8f61b6bba24a52f73c4500eafc2a03b300a89 Mon Sep 17 00:00:00 2001 From: Ale Date: Thu, 19 Mar 2026 16:14:41 +0900 Subject: [PATCH 12/25] [Aseprite] Add Image Settings for scale and padding control - Added Image Scale option to adjust the resolution of exported images. - Added Image Padding setting to define pixel padding around image borders. --- aseprite/Prepare-For-Spine.lua | 296 +++++++++++++++++++++++++++------ 1 file changed, 243 insertions(+), 53 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 4357054..7b4c409 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -117,6 +117,8 @@ outputPath: the output json file path clearOldImages: if true, clear existing images folder before export originX, originY: the user-defined origin point for the exported Spine skeleton, as a percentage of the sprite's width and height (range 0-1) roundCoordinatesToInteger: if true, rounds the attachment coordinates to the nearest integer instead of keeping decimals (not recommended for pixel art) +imageScalePercent: the scale percentage to apply to exported image resolution +imagePaddingPx: the padding to apply around each captured image, in pixels ]] function captureLayers( layers, @@ -126,7 +128,9 @@ function captureLayers( clearOldImages, originX, originY, - roundCoordinatesToInteger) + roundCoordinatesToInteger, + imageScalePercent, + imagePaddingPx) -- Default output path to the sprite-name json in the sprite's directory. if (outputPath == nil or outputPath == "") then local defaultOutputDir = app.fs.filePath(sprite.filename) @@ -186,6 +190,7 @@ function captureLayers( local slotsJson = {} local skinsJson = {} local index = 1 + local scaleFactor = imageScalePercent / 100 -- convert from percentage to a multiplier (e.g. 100% -> 1, 50% -> 0.5, 200% -> 2) for i, layer in ipairs(layers) do -- Ignore groups and non-visible layers if (not layer.isGroup and effectiveVisibilities[i] == true) then @@ -196,7 +201,18 @@ function captureLayers( local savedOk = false savedOk = pcall(function() local cropped = Sprite(sprite) - cropped:crop(cel.position.x, cel.position.y, cel.bounds.width, cel.bounds.height) + local cropX = cel.position.x - imagePaddingPx + local cropY = cel.position.y - imagePaddingPx + local cropWidth = cel.bounds.width + imagePaddingPx * 2 + local cropHeight = cel.bounds.height + imagePaddingPx * 2 + cropped:crop(cropX, cropY, cropWidth, cropHeight) + + local scaledWidth = math.max(1, math.floor(cropWidth * scaleFactor + 0.5)) + local scaledHeight = math.max(1, math.floor(cropHeight * scaleFactor + 0.5)) + if (scaledWidth ~= cropWidth or scaledHeight ~= cropHeight) then + cropped:resize(scaledWidth, scaledHeight) + end + cropped:saveCopyAs(imagePath) cropped:close() end) @@ -208,6 +224,8 @@ function captureLayers( -- Calculate the attachment position based on the cel position, cel bounds, sprite bounds, and the user-defined originX and originY. local attachmentX = cel.bounds.width / 2 + cel.position.x - sprite.bounds.width * originX local attachmentY = sprite.bounds.height * (1 - originY) - cel.position.y - cel.bounds.height / 2 + attachmentX = attachmentX * scaleFactor + attachmentY = attachmentY * scaleFactor slotsJson[index] = string.format([[ { "name": "%s", "bone": "%s", "attachment": "%s" } ]], name, "root", name) -- If roundCoordinatesToInteger is true, round the attachmentX and attachmentY to the nearest integer using math.modf. Otherwise, keep the decimal values with 3 decimal places. if (roundCoordinatesToInteger == true) then @@ -314,6 +332,10 @@ function showExportOptionsDialog() setOriginMode(optionsDialog, ORIGIN_MODE.NORMALIZED) optionsDialog:modify({ id = "originX", text = string.format("%.3f", 0.5) }) optionsDialog:modify({ id = "originY", text = string.format("%.3f", 0) }) + optionsDialog:modify({ id = "imageScalePercent", text = string.format("%.3f", 100) }) + optionsDialog:modify({ id = "imageScaleSlider", value = IMAGE_SCALE_SLIDER_MAX / 10 }) + optionsDialog:modify({ id = "imagePaddingPx", text = string.format("%.0f", 1) }) + optionsDialog:modify({ id = "imagePaddingSlider", value = 1 }) optionsDialog:modify({ id = "roundCoordinatesToInteger", selected = false }) optionsDialog:modify({ id = "outputPath", text = defaultOutputPath }) optionsDialog:modify({ id = "ignoreHiddenLayers", selected = true }) @@ -325,7 +347,6 @@ function showExportOptionsDialog() --#endregion --#region Coordinate Settings - optionsDialog:label({ id = "coordinateSettings", label = "Coordinate Settings", @@ -376,7 +397,7 @@ function showExportOptionsDialog() max = ORIGIN_SLIDER_STEPS, value = 0, onchange = function() - applyOriginSliderChange(optionsDialog, "x") + syncOriginSlidersToFields(optionsDialog, "x") end }) :slider({ @@ -385,7 +406,7 @@ function showExportOptionsDialog() max = ORIGIN_SLIDER_STEPS, value = 0, onchange = function() - applyOriginSliderChange(optionsDialog, "y") + syncOriginSlidersToFields(optionsDialog, "y") end }) applyOriginMode(optionsDialog) @@ -425,30 +446,58 @@ function showExportOptionsDialog() text = "Drop decimal pixels, May misalign pixels; not recommended for pixel art.", selected = cachedOptions.roundCoordinatesToInteger }) + optionsDialog:separator({}) ---#endregion + --#endregion - --#region Output Path Settings - -- entry: Output json path - optionsDialog:entry({ - id = "outputPath", - label = "Output Path", - text = cachedOptions.outputPath + --#region Image Settings + optionsDialog:label({ + id = "imageSettings", + label = "Image Settings", + text = "Configure output image transform settings." }) - -- file: File picker to select output json path (syncs with entry) - optionsDialog:file({ - id = "outputPathPicker", - title = "Select Output Path", - filename = cachedOptions.outputPath, - text = "Select Output Path", - save = true, + + -- number + slider: Image scale as a percentage, where 100% means the same size as the original sprite. + optionsDialog:number({ + id = "imageScalePercent", + label = "Scale (%)", + text = string.format("%.3f", cachedOptions.imageScalePercent), + decimals = 3, onchange = function() - local selectedPath = optionsDialog.data.outputPathPicker - if (selectedPath ~= nil and selectedPath ~= "") then - optionsDialog:modify({ id = "outputPath", text = selectedPath }) - end + clampImageScaleFieldValue(optionsDialog) + end + }) + :slider({ + id = "imageScaleSlider", + min = 0, + max = IMAGE_SCALE_SLIDER_MAX, + onchange = function() + syncImageScaleSliderToField(optionsDialog) end }) + syncImageScaleSliderFromField(optionsDialog) + optionsDialog:newrow() + + -- number + slider: Image padding in pixels. + optionsDialog:number({ + id = "imagePaddingPx", + label = "Padding (px)", + text = string.format("%.0f", cachedOptions.imagePaddingPx), + decimals = 0, + onchange = function() + clampImagePaddingFieldValue(optionsDialog) + end + }) + :slider({ + id = "imagePaddingSlider", + min = 0, + max = IMAGE_PADDING_SLIDER_MAX, + onchange = function() + syncImagePaddingSliderToField(optionsDialog) + end + }) + syncImagePaddingSliderFromField(optionsDialog) + optionsDialog:separator({}) --#endregion @@ -465,12 +514,36 @@ function showExportOptionsDialog() optionsDialog:check({ id = "clearOldImages", label = "Clear Old Images", - text = "Delete existing images first.", + text = "Delete existing images first, including leftovers from removed layers.", selected = cachedOptions.clearOldImages }) optionsDialog:separator({}) --#endregion + --#region Output Path Settings + -- entry: Output json path + optionsDialog:entry({ + id = "outputPath", + label = "Output Path", + text = cachedOptions.outputPath + }) + -- file: File picker to select output json path (syncs with entry) + optionsDialog:file({ + id = "outputPathPicker", + title = "Select Output Path", + filename = cachedOptions.outputPath, + text = "Select Output Path", + save = true, + onchange = function() + local selectedPath = optionsDialog.data.outputPathPicker + if (selectedPath ~= nil and selectedPath ~= "") then + optionsDialog:modify({ id = "outputPath", text = selectedPath }) + end + end + }) + optionsDialog:separator({}) + --#endregion + --#region Execution Buttons -- button: Confirm export local confirmed = false @@ -503,7 +576,7 @@ function showExportOptionsDialog() if (options.outputPath == nil or options.outputPath == "") then options.outputPath = defaultOutputPath end - -- Parse originX and originY as numbers, and fallback to defaults if parsing fails or values are out of range + -- Parse originX and originY as numbers, and fallback to defaults if parsing fails or values are out of range. if (options.originMode ~= ORIGIN_MODE.PIXEL and options.originMode ~= ORIGIN_MODE.NORMALIZED) then options.originMode = ORIGIN_MODE.NORMALIZED end @@ -516,6 +589,12 @@ function showExportOptionsDialog() options.originX = clampValue(parsedOriginX or 0.5, 0, 1) options.originY = clampValue(parsedOriginY or 0, 0, 1) end + -- Parse imageScalePercent as a number, and fallback to default if parsing fails or value is out of range. + local parsedImageScalePercent = tonumber(options.imageScalePercent) + options.imageScalePercent = clampValue(parsedImageScalePercent or 100, 0, IMAGE_SCALE_VALUE_MAX) + -- Parse imagePaddingPx as a number, and fallback to default if parsing fails or value is out of range. + local parsedImagePaddingPx = tonumber(options.imagePaddingPx) + options.imagePaddingPx = clampValue(math.floor((parsedImagePaddingPx or 1) + 0.5), 0, IMAGE_PADDING_INPUT_MAX) -- Save the options to cache so they will be remembered next time the dialog is opened. saveCachedOptions(configPath, options) @@ -531,8 +610,8 @@ end --[[ Shows export completion dialog with action to open the exported file location. -jsonFileName: The exported json file path -failedPaths: The list of file/directory paths that failed to write +jsonFileName: The exported json file path. +failedPaths: The list of file/directory paths that failed to write. ]] function showExportCompletedDialog(jsonFileName, failedPaths) local completedDialog = Dialog({ title = "Export Completed" }) @@ -553,7 +632,7 @@ function showExportCompletedDialog(jsonFileName, failedPaths) completedDialog:label({ text = "Failed to write:" }) - -- List each failed path + -- List each failed path. for _, path in ipairs(failedPaths) do completedDialog:newrow() completedDialog:label({ @@ -563,7 +642,7 @@ function showExportCompletedDialog(jsonFileName, failedPaths) end completedDialog:newrow() - -- Button to open the file location in the OS file explorer + -- Button to open the file location in the OS file explorer. completedDialog:button({ text = "Open File Folder", onclick = function() @@ -571,7 +650,7 @@ function showExportCompletedDialog(jsonFileName, failedPaths) completedDialog:close() end }) - -- Button to close the dialog + -- Button to close the dialog. completedDialog:button({ text = "OK", focus = true, @@ -583,7 +662,7 @@ function showExportCompletedDialog(jsonFileName, failedPaths) completedDialog:show({ wait = true }) end ---#region Coordinates Origin Mode Functions +--#region Coordinates Settings Functions ORIGIN_MODE = { NORMALIZED = "Normalized", -- Normalized origin coordinates in the range [0,1], where (0,0) is the bottom-left. PIXEL = "Pixel", -- Pixel-based origin coordinates, where (0,0) is the bottom-left of the sprite and values are in pixels. @@ -730,6 +809,8 @@ function clampOriginXyFieldValue(optionsDialog) local clampedY = clampValue(parsedY, 0, maxY) optionsDialog:modify({ id = "originX", text = string.format("%.3f", clampedX) }) optionsDialog:modify({ id = "originY", text = string.format("%.3f", clampedY) }) + + -- After clamping the field values, also update the slider positions to match the clamped values. syncOriginSlidersFromFields(optionsDialog) end @@ -837,25 +918,6 @@ end --#region Origin Coordinate Sliders Functions --- Converts a coordinate value into slider step value based on current mode range. -function toOriginSliderValue(value, maxValue) - if (maxValue == nil or maxValue <= 0) then - maxValue = 1 - end - local normalized = clampValue((value or 0) / maxValue, 0, 1) - return math.floor(normalized * ORIGIN_SLIDER_STEPS + 0.5) -end - --- Converts a slider step value back into coordinate value based on current mode range. -function fromOriginSliderValue(sliderValue, maxValue) - if (maxValue == nil or maxValue <= 0) then - maxValue = 1 - end - local step = clampValue(sliderValue or 0, 0, ORIGIN_SLIDER_STEPS) - local normalized = step / ORIGIN_SLIDER_STEPS - return normalized * maxValue -end - -- Syncs the slider positions from current originX/originY input values. function syncOriginSlidersFromFields(optionsDialog) if (ORIGIN_SLIDER_IS_SYNCING) then @@ -889,8 +951,8 @@ function syncOriginSlidersFromFields(optionsDialog) ORIGIN_SLIDER_IS_SYNCING = false end --- Applies slider movement back to originX/originY inputs. -function applyOriginSliderChange(optionsDialog, axis) +-- Syncs the originX and originY input fields from the current slider positions. +function syncOriginSlidersToFields(optionsDialog, axis) if (ORIGIN_SLIDER_IS_SYNCING) then return end @@ -922,6 +984,126 @@ function applyOriginSliderChange(optionsDialog, axis) clampOriginXyFieldValue(optionsDialog) end + +-- Converts a coordinate value into slider step value based on current mode range. +function toOriginSliderValue(value, maxValue) + if (maxValue == nil or maxValue <= 0) then + maxValue = 1 + end + local normalized = clampValue((value or 0) / maxValue, 0, 1) + return math.floor(normalized * ORIGIN_SLIDER_STEPS + 0.5) +end + +-- Converts a slider step value back into coordinate value based on current mode range. +function fromOriginSliderValue(sliderValue, maxValue) + if (maxValue == nil or maxValue <= 0) then + maxValue = 1 + end + local step = clampValue(sliderValue or 0, 0, ORIGIN_SLIDER_STEPS) + local normalized = step / ORIGIN_SLIDER_STEPS + return normalized * maxValue +end +--#endregion +--#endregion + +--#region Image Settings Functions +IMAGE_SCALE_SLIDER_MAX = 1000 +IMAGE_SCALE_SLIDER_IS_SYNCING = false +IMAGE_SCALE_VALUE_MAX = 10000 +IMAGE_PADDING_SLIDER_MAX = 4 +IMAGE_PADDING_IS_SYNCING = false +IMAGE_PADDING_INPUT_MAX = 100 + +--#region Image Scale Slider Functions + +-- Clamps the image scale input to a minimum of 0 and updates the slider state. +function clampImageScaleFieldValue(optionsDialog) + local parsedScale = tonumber(optionsDialog.data.imageScalePercent) + if (parsedScale == nil) then + parsedScale = 100 + end + local clampedScale = clampValue(parsedScale, 0, IMAGE_SCALE_VALUE_MAX) + optionsDialog:modify({ id = "imageScalePercent", text = string.format("%.3f", clampedScale) }) + syncImageScaleSliderFromField(optionsDialog) +end + +-- Syncs slider value from the scale input field; input above slider max keeps slider at max. +function syncImageScaleSliderFromField(optionsDialog) + if (IMAGE_SCALE_SLIDER_IS_SYNCING) then + return + end + + local parsedScale = tonumber(optionsDialog.data.imageScalePercent) + if (parsedScale == nil) then + parsedScale = 100 + end + local clampedScale = clampValue(parsedScale, 0, IMAGE_SCALE_VALUE_MAX) + local sliderValue = clampValue(math.floor(clampedScale + 0.5), 0, IMAGE_SCALE_SLIDER_MAX) + + IMAGE_SCALE_SLIDER_IS_SYNCING = true + optionsDialog:modify({ id = "imageScaleSlider", value = sliderValue }) + IMAGE_SCALE_SLIDER_IS_SYNCING = false +end + +-- Syncs the scale input field from the slider value. +function syncImageScaleSliderToField(optionsDialog) + if (IMAGE_SCALE_SLIDER_IS_SYNCING) then + return + end + + local sliderValue = tonumber(optionsDialog.data.imageScaleSlider) or 0 + sliderValue = clampValue(sliderValue, 0, IMAGE_SCALE_SLIDER_MAX) + + IMAGE_SCALE_SLIDER_IS_SYNCING = true + optionsDialog:modify({ id = "imageScalePercent", text = string.format("%.3f", sliderValue) }) + IMAGE_SCALE_SLIDER_IS_SYNCING = false +end +--#endregion + +--#region Image Padding Slider Functions + +-- Clamps the image padding input to [0,100] and updates the slider state. +function clampImagePaddingFieldValue(optionsDialog) + local parsedPadding = tonumber(optionsDialog.data.imagePaddingPx) + if (parsedPadding == nil) then + parsedPadding = 0 + end + local clampedPadding = clampValue(math.floor(parsedPadding + 0.5), 0, IMAGE_PADDING_INPUT_MAX) + optionsDialog:modify({ id = "imagePaddingPx", text = string.format("%.0f", clampedPadding) }) + syncImagePaddingSliderFromField(optionsDialog) +end + +-- Syncs padding slider value from the input field; input above slider max keeps slider at max. +function syncImagePaddingSliderFromField(optionsDialog) + if (IMAGE_PADDING_IS_SYNCING) then + return + end + + local parsedPadding = tonumber(optionsDialog.data.imagePaddingPx) + if (parsedPadding == nil) then + parsedPadding = 0 + end + local clampedPadding = clampValue(math.floor(parsedPadding + 0.5), 0, IMAGE_PADDING_INPUT_MAX) + local sliderValue = clampValue(clampedPadding, 0, IMAGE_PADDING_SLIDER_MAX) + + IMAGE_PADDING_IS_SYNCING = true + optionsDialog:modify({ id = "imagePaddingSlider", value = sliderValue }) + IMAGE_PADDING_IS_SYNCING = false +end + +-- Syncs the padding input field from the slider value. +function syncImagePaddingSliderToField(optionsDialog) + if (IMAGE_PADDING_IS_SYNCING) then + return + end + + local sliderValue = tonumber(optionsDialog.data.imagePaddingSlider) or 0 + sliderValue = clampValue(math.floor(sliderValue + 0.5), 0, IMAGE_PADDING_SLIDER_MAX) + + IMAGE_PADDING_IS_SYNCING = true + optionsDialog:modify({ id = "imagePaddingPx", text = string.format("%.0f", sliderValue) }) + IMAGE_PADDING_IS_SYNCING = false +end --#endregion --#endregion @@ -949,6 +1131,8 @@ function loadCachedOptions(defaultOutputPath) local cached = { originX = 0.5, originY = 0, + imageScalePercent = 100, + imagePaddingPx = 1, originMode = ORIGIN_MODE.NORMALIZED, roundCoordinatesToInteger = false, outputPath = defaultOutputPath, @@ -975,6 +1159,8 @@ function loadCachedOptions(defaultOutputPath) cached.originX = tonumber(raw.originX) or cached.originX cached.originY = tonumber(raw.originY) or cached.originY + cached.imageScalePercent = clampValue(tonumber(raw.imageScalePercent) or cached.imageScalePercent, 0, IMAGE_SCALE_VALUE_MAX) + cached.imagePaddingPx = clampValue(math.floor((tonumber(raw.imagePaddingPx) or cached.imagePaddingPx) + 0.5), 0, IMAGE_PADDING_INPUT_MAX) if (raw.originMode == ORIGIN_MODE.PIXEL or raw.originMode == ORIGIN_MODE.NORMALIZED) then cached.originMode = raw.originMode end @@ -1001,6 +1187,8 @@ function saveCachedOptions(configPath, options) configFile:write("originX=" .. string.format("%.3f", options.originX) .. "\n") configFile:write("originY=" .. string.format("%.3f", options.originY) .. "\n") + configFile:write("imageScalePercent=" .. string.format("%.3f", options.imageScalePercent or 100) .. "\n") + configFile:write("imagePaddingPx=" .. string.format("%.0f", options.imagePaddingPx or 0) .. "\n") configFile:write("originMode=" .. (options.originMode or ORIGIN_MODE.NORMALIZED) .. "\n") configFile:write("roundCoordinatesToInteger=" .. tostring(options.roundCoordinatesToInteger == true) .. "\n") configFile:write("outputPath=" .. (options.outputPath or "") .. "\n") @@ -1055,7 +1243,9 @@ captureLayers( options.clearOldImages, normalizedOriginX, normalizedOriginY, - options.roundCoordinatesToInteger + options.roundCoordinatesToInteger, + options.imageScalePercent, + options.imagePaddingPx ) -- Restore the layer's visibilities to how they were before From ae36f536843b3625239c47deffff6bbef037d802 Mon Sep 17 00:00:00 2001 From: Ale Date: Thu, 19 Mar 2026 19:15:10 +0900 Subject: [PATCH 13/25] [Aseprite] Support [origin] layer, add Spine logo, and refine UI layout - Added support for [origin] tag: The plugin now automatically uses the center of any layer named [origin] as the export origin. - Added Spine Logo to the dialog header for better branding/recognition. - Refined UI Layout: Optimized spacing and alignment of all control panels for a cleaner look. --- aseprite/Images/Prepare-For-Spine-Logo.png | Bin 0 -> 485 bytes aseprite/Prepare-For-Spine.lua | 279 +++++++++++++++++++-- 2 files changed, 258 insertions(+), 21 deletions(-) create mode 100644 aseprite/Images/Prepare-For-Spine-Logo.png diff --git a/aseprite/Images/Prepare-For-Spine-Logo.png b/aseprite/Images/Prepare-For-Spine-Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f76a0ed59ce9f76b73785b1b145853dd52da1bc7 GIT binary patch literal 485 zcmVPx$pGibPRA_tes{gAAQ1j0 zkV?HnJVN-vWgX`}F?x8tU39@@BmuqaqN(}B=pR=baaX(*Z+pMu_I;ScrM5^gAsiHq z!lID32-Q7dui(j5#%28~3)9Hn8>Ug!t`<11BW#wAtF<}naa2cKc}=^$P?Ux$HO8L3 z+&ytwvvs7$!JDD#5+iUs*d{J(_9;SfN;V4Yd9aKdH#b*Yhhf(^+)ZN^vqP6l>~nR8y;eFp z-%Flnb(0Y7s#joDu*5Ci$63Uz$Kg%Fng^PAb)LiWI@gZ}3+Ts#74(yQC4WxxAzeTk b{NIsZ(Vy~+kQ4s$00000NkvXXu0mjf8yMV) literal 0 HcmV?d00001 diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 7b4c409..01b5fdb 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -43,7 +43,7 @@ function containsDuplicates(layers, visibilities) local duplicateNames = {} -- List of layer duplicates names -- Iterate through the layers and count the occurrences of each name among visible layers for i, layer in ipairs(layers) do - if (not layer.isGroup and visibilities[i] == true) then + if (not layer.isGroup and visibilities[i] == true and not isMarkerLayer(layer)) then local name = layer.name local count = (nameCounts[name] or 0) + 1 nameCounts[name] = count @@ -193,7 +193,7 @@ function captureLayers( local scaleFactor = imageScalePercent / 100 -- convert from percentage to a multiplier (e.g. 100% -> 1, 50% -> 0.5, 200% -> 2) for i, layer in ipairs(layers) do -- Ignore groups and non-visible layers - if (not layer.isGroup and effectiveVisibilities[i] == true) then + if (not layer.isGroup and effectiveVisibilities[i] == true and not isMarkerLayer(layer)) then -- Set the layer to visible so we can capture it, then set it back to hidden after layer.isVisible = true local cel = layer.cels[1] @@ -306,6 +306,96 @@ function openFileLocation(filePath) end end +--#region Layer Marker Functions + +--[[ +Finds the first non-group layer whose name contains [] marker text by recursive layer order. +parent: The sprite or parent layer group +markerName: Marker text name to match inside [] (case-insensitive) +]] +function findFirstMarkerLayer(parent, markerName) + for _, layer in ipairs(parent.layers) do + if (isMarkerLayer(layer)) then + if (markerName == nil or markerName == "" or hasMarkerName(layer.name, markerName)) then + return layer + end + end + if (layer.isGroup) then + local found = findFirstMarkerLayer(layer, markerName) + if (found ~= nil) then + return found + end + end + end + return nil +end + +--[[ +Returns true when a layer is a non-group marker layer. +layer: the layer to check +]] +function isMarkerLayer(layer) + if (layer == nil or layer.isGroup) then + return false + end + + if (layer.name == nil) then + return false + end + + return string.find(layer.name, "%b[]") ~= nil +end + +--[[ +Returns true when layerName contains the exact marker text inside [] (case-insensitive). +layerName: the layer name to check for the marker text +markerName: the marker text to match inside [] (case-insensitive) +]] +function hasMarkerName(layerName, markerName) + if (layerName == nil or markerName == nil or markerName == "") then + return false + end + + local escapedMarker = string.gsub(string.lower(markerName), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") + local markerPattern = "%[" .. escapedMarker .. "%]" + return string.find(string.lower(layerName), markerPattern) ~= nil +end + +--[[ +Gets marker-center coordinates as origin values in the requested mode. +sprite: the sprite to search for marker layers +mode: the origin mode to return values in (ORIGIN_MODE.PIXEL or ORIGIN_MODE.NORMALIZED) +]] +function getOriginFromMarkerLayer(sprite, mode) + if (sprite == nil) then + return nil, nil + end + -- Find the first non-group layer whose name contains [origin] by recursive layer order. + local markerLayer = findFirstMarkerLayer(sprite, "origin") + if (markerLayer == nil or markerLayer.cels == nil or #markerLayer.cels == 0) then + return nil, nil + end + -- Calculate the center of the cel bounds as the marker position, and convert to the requested mode. + local cel = markerLayer.cels[1] + if (cel == nil or cel.bounds == nil or cel.position == nil) then + return nil, nil + end + local spriteWidth, spriteHeight = getActiveSpriteSize() + local centerX = cel.bounds.x + cel.bounds.width * 0.5 + local centerYFromTop = cel.bounds.y + cel.bounds.height * 0.5 + local pixelYFromBottom = spriteHeight - centerYFromTop + + -- Clamp the returned values to valid ranges for the selected mode, in case the marker layer is placed outside the sprite bounds. + if (mode == ORIGIN_MODE.PIXEL) then + return clampValue(centerX, 0, spriteWidth), clampValue(pixelYFromBottom, 0, spriteHeight) + elseif (mode == ORIGIN_MODE.NORMALIZED) then + return clampValue(centerX / spriteWidth, 0, 1), clampValue(pixelYFromBottom / spriteHeight, 0, 1) + end + + return nil, nil +end +--#endregion + -----------------------------------------------[[ UI Functions ]]----------------------------------------------- --[[ @@ -323,6 +413,9 @@ function showExportOptionsDialog() local defaultOutputPath = spriteOutputDir .. app.fs.pathSeparator .. spriteOutputName .. ".json" local cachedOptions, configPath = loadCachedOptions(defaultOutputPath) CURRENT_ORIGIN_MODE = cachedOptions.originMode + + -- Draw the Spine logo at the top. + DrawSpineLogo(optionsDialog) --#region Other Buttons -- button: Resets all options to their default values @@ -340,6 +433,8 @@ function showExportOptionsDialog() optionsDialog:modify({ id = "outputPath", text = defaultOutputPath }) optionsDialog:modify({ id = "ignoreHiddenLayers", selected = true }) optionsDialog:modify({ id = "clearOldImages", selected = false }) + optionsDialog:repaint() + app:refresh() end }) @@ -352,6 +447,16 @@ function showExportOptionsDialog() label = "Coordinate Settings", text = "Set which position is used as the Spine origin (0,0). Range: [0,1]." }) + optionsDialog:newrow() + -- Get origin coordinates from the first [origin] marker layer if present. + local markerOriginX, markerOriginY = getOriginFromMarkerLayer(activeSprite, getOriginMode()) + local markerOriginApplied = markerOriginX ~= nil and markerOriginY ~= nil + -- label: Shows whether the origin was set from the [origin] marker layer. + optionsDialog:label({ + id = "originMarkerStatus", + text = markerOriginApplied and "✅ Origin set from [origin] marker layer." or "⚪ Origin not set from [origin] marker layer." + }) + -- radio: Option to choose between normalized coordinates (0-1) or pixel-based coordinates optionsDialog:radio({ id = "originModeNormalized", @@ -372,7 +477,9 @@ function showExportOptionsDialog() applyOriginMode(optionsDialog) end }) + -- Set the initial state of the origin mode radio buttons based on cached options. setOriginMode(optionsDialog, cachedOptions.originMode) + -- number + slider: Coordinate origin X and Y. optionsDialog:number({ id = "originX", @@ -448,6 +555,13 @@ function showExportOptionsDialog() }) optionsDialog:separator({}) + + -- Override origin values from the first [origin] marker layer if present. + if (markerOriginApplied) then + optionsDialog:modify({ id = "originX", text = string.format("%.3f", markerOriginX) }) + optionsDialog:modify({ id = "originY", text = string.format("%.3f", markerOriginY) }) + clampOriginXyFieldValue(optionsDialog) + end --#endregion --#region Image Settings @@ -501,26 +615,12 @@ function showExportOptionsDialog() optionsDialog:separator({}) --#endregion - --#region Other Settings - -- check: Option to skip exporting hidden layers (including layers under hidden groups) - optionsDialog:check({ - id = "ignoreHiddenLayers", - label = "Ignore Hidden Layers", - text = "Hidden layers and layers under hidden groups will not be output.", - selected = cachedOptions.ignoreHiddenLayers - }) - - -- check: Option to clear old images in the output images directory before export - optionsDialog:check({ - id = "clearOldImages", - label = "Clear Old Images", - text = "Delete existing images first, including leftovers from removed layers.", - selected = cachedOptions.clearOldImages + --#region Output Settings + optionsDialog:label({ + id = "outputSettings", + label = "Output Settings", + text = "Configure the export JSON and image paths, and set output behavior." }) - optionsDialog:separator({}) - --#endregion - - --#region Output Path Settings -- entry: Output json path optionsDialog:entry({ id = "outputPath", @@ -541,6 +641,23 @@ function showExportOptionsDialog() end end }) + + -- check: Option to skip exporting hidden layers (including layers under hidden groups) + optionsDialog:check({ + id = "ignoreHiddenLayers", + label = "Ignore Hidden Layers", + text = "Hidden layers and layers under hidden groups will not be output.", + selected = cachedOptions.ignoreHiddenLayers + }) + + -- check: Option to clear old images in the output images directory before export + optionsDialog:check({ + id = "clearOldImages", + label = "Clear Old Images", + text = "Delete existing images first, including leftovers from removed layers.", + selected = cachedOptions.clearOldImages + }) + optionsDialog:separator({}) --#endregion @@ -565,8 +682,24 @@ function showExportOptionsDialog() }) --#endregion + --#region Show MainUI + -- Delay one frame before refreshing so the logo canvas gets painted reliably. + local firstFrameRefreshTimer + firstFrameRefreshTimer = Timer({ + interval = 1 / 60, + ontick = function() + if (firstFrameRefreshTimer ~= nil) then + firstFrameRefreshTimer:stop() + end + optionsDialog:repaint() + app:refresh() + end + }) + firstFrameRefreshTimer:start() + -- Show the dialog and wait for user input. optionsDialog:show({ wait = true}) + --#endregion --#region options Data Extraction -- Get the selected options @@ -608,6 +741,8 @@ function showExportOptionsDialog() return options end +--#region Info Dialog Functions + --[[ Shows export completion dialog with action to open the exported file location. jsonFileName: The exported json file path. @@ -661,6 +796,7 @@ function showExportCompletedDialog(jsonFileName, failedPaths) completedDialog:show({ wait = true }) end +--#endregion --#region Coordinates Settings Functions ORIGIN_MODE = { @@ -1198,6 +1334,107 @@ function saveCachedOptions(configPath, options) end --#endregion +--#region Other Functions + +-- Draws a consistent pixel-grid Spine logo on the options dialog. +function DrawSpineLogo(optionsDialog) + if (optionsDialog == nil) then + return + end + + -- load the logo image from cache. + local cacheDir = app.fs.joinPath(app.fs.filePath(app.fs.userConfigPath), "Cache") + app.fs.makeDirectory(cacheDir) + local logoPath = app.fs.joinPath(cacheDir, "Prepare-For-Spine-Logo.png") + local loadedImage = nil + local cacheFile = io.open(logoPath, "rb") + local hasCachedLogo = cacheFile ~= nil + if hasCachedLogo then + cacheFile:close() + local loadedOk, imageOrError = pcall(function() + return Image({ fromFile = logoPath }) + end) + if (loadedOk == true and imageOrError ~= nil) then + loadedImage = imageOrError + end + end + + -- if loading from cache failed, attempt to download the logo image and save it to cache, then load it. + if (loadedImage == nil) then + local logoUrl = "https://github.com/EsotericSoftware/spine-scripts/blob/master/aseprite/Images/Spine-Logo.png" + local downloadOk = false + if (app.fs.pathSeparator == "\\") then + local cmd = 'powershell -NoProfile -Command "try { Invoke-WebRequest -Uri \"' .. logoUrl .. '\" -OutFile \"' .. logoPath .. '\" -UseBasicParsing; exit 0 } catch { exit 1 }"' + local result = os.execute(cmd) + downloadOk = result == true or result == 0 + else + local cmd = 'curl -L -o "' .. logoPath .. '" "' .. logoUrl .. '"' + local result = os.execute(cmd) + downloadOk = result == true or result == 0 + end + + if (downloadOk == true) then + local loadedOk, imageOrError = pcall(function() + return Image({ fromFile = logoPath }) + end) + if (loadedOk == true and imageOrError ~= nil) then + loadedImage = imageOrError + end + end + end + + -- Draw the logo image on a canvas in the options dialog, or show an error message if loading failed. + if (loadedImage == nil) then + optionsDialog:label({ + id = "spineLogoStatus", + text = "Logo render failed (download/cache load)." + }) + optionsDialog:newrow() + return + else + local displayScale = 2 + local displayImage = loadedImage + -- Build a 2x nearest-neighbor display image for clearer pixel-art rendering in the dialog. + local scaledOk, scaledOrError = pcall(function() + local scaled = Image(loadedImage.width * displayScale, loadedImage.height * displayScale, loadedImage.colorMode) + for y = 0, loadedImage.height - 1 do + for x = 0, loadedImage.width - 1 do + local px = loadedImage:getPixel(x, y) + local sx = x * displayScale + local sy = y * displayScale + scaled:putPixel(sx, sy, px) + scaled:putPixel(sx + 1, sy, px) + scaled:putPixel(sx, sy + 1, px) + scaled:putPixel(sx + 1, sy + 1, px) + end + end + return scaled + end) + if (scaledOk == true and scaledOrError ~= nil) then + displayImage = scaledOrError + end + + local minCanvasWidth = 360 + local canvasWidth = math.max(displayImage.width, minCanvasWidth) + local canvasHeight = displayImage.height + local drawX = math.floor((canvasWidth - displayImage.width) * 0.5) + local drawY = 0 + optionsDialog:canvas({ + id = "spineLogoCanvas", + width = canvasWidth, + height = canvasHeight, + onpaint = function(ev) + local gc = ev.context + gc.antialias = false + gc:drawImage(displayImage, drawX, drawY) + end + }) + end + + optionsDialog:separator({}) +end +--#endregion + -----------------------------------------------[[ Main Execution ]]----------------------------------------------- local activeSprite = app.activeSprite From 289592d24c138a6daf6a508ce121bd38f1fa3b74 Mon Sep 17 00:00:00 2001 From: Ale Date: Thu, 19 Mar 2026 21:14:43 +0900 Subject: [PATCH 14/25] [Aseprite] Update README to v1.3 - Update the document content to v1.3 - Code organization and optimization. --- aseprite/Images/image-1.png | Bin 9005 -> 16069 bytes aseprite/Prepare-For-Spine.lua | 131 +++++++++++++-------------------- aseprite/README.md | 77 +++++++++++++------ aseprite/README_cn.md | 77 +++++++++++++------ 4 files changed, 157 insertions(+), 128 deletions(-) diff --git a/aseprite/Images/image-1.png b/aseprite/Images/image-1.png index 6e2e6647735cb253cb0d2264e3d6cdf3bd507034..c57e4c9dec29de118841ed55bc9e5bba6fde11a6 100644 GIT binary patch literal 16069 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 k_6j8MmNp2KDNyT8R@Tkn$|U>RWz>% literal 9005 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 Mv%AxcPfmRCzsF^8A^-pY diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 01b5fdb..df6b7b9 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -412,7 +412,6 @@ function showExportOptionsDialog() local spriteOutputName = app.fs.fileTitle(activeSprite.filename) local defaultOutputPath = spriteOutputDir .. app.fs.pathSeparator .. spriteOutputName .. ".json" local cachedOptions, configPath = loadCachedOptions(defaultOutputPath) - CURRENT_ORIGIN_MODE = cachedOptions.originMode -- Draw the Spine logo at the top. DrawSpineLogo(optionsDialog) @@ -423,8 +422,7 @@ function showExportOptionsDialog() text = "Reset Config", onclick = function() setOriginMode(optionsDialog, ORIGIN_MODE.NORMALIZED) - optionsDialog:modify({ id = "originX", text = string.format("%.3f", 0.5) }) - optionsDialog:modify({ id = "originY", text = string.format("%.3f", 0) }) + setOriginPreset(optionsDialog, "bottom-center") optionsDialog:modify({ id = "imageScalePercent", text = string.format("%.3f", 100) }) optionsDialog:modify({ id = "imageScaleSlider", value = IMAGE_SCALE_SLIDER_MAX / 10 }) optionsDialog:modify({ id = "imagePaddingPx", text = string.format("%.0f", 1) }) @@ -448,13 +446,9 @@ function showExportOptionsDialog() text = "Set which position is used as the Spine origin (0,0). Range: [0,1]." }) optionsDialog:newrow() - -- Get origin coordinates from the first [origin] marker layer if present. - local markerOriginX, markerOriginY = getOriginFromMarkerLayer(activeSprite, getOriginMode()) - local markerOriginApplied = markerOriginX ~= nil and markerOriginY ~= nil -- label: Shows whether the origin was set from the [origin] marker layer. optionsDialog:label({ - id = "originMarkerStatus", - text = markerOriginApplied and "✅ Origin set from [origin] marker layer." or "⚪ Origin not set from [origin] marker layer." + id = "originMarkerStatus" }) -- radio: Option to choose between normalized coordinates (0-1) or pixel-based coordinates @@ -465,7 +459,6 @@ function showExportOptionsDialog() selected = cachedOptions.originMode == ORIGIN_MODE.NORMALIZED, onclick = function() setOriginMode(optionsDialog, ORIGIN_MODE.NORMALIZED) - applyOriginMode(optionsDialog) end }) optionsDialog:radio({ @@ -474,12 +467,9 @@ function showExportOptionsDialog() selected = cachedOptions.originMode == ORIGIN_MODE.PIXEL, onclick = function() setOriginMode(optionsDialog, ORIGIN_MODE.PIXEL) - applyOriginMode(optionsDialog) end }) - -- Set the initial state of the origin mode radio buttons based on cached options. - setOriginMode(optionsDialog, cachedOptions.originMode) - + -- number + slider: Coordinate origin X and Y. optionsDialog:number({ id = "originX", @@ -504,7 +494,7 @@ function showExportOptionsDialog() max = ORIGIN_SLIDER_STEPS, value = 0, onchange = function() - syncOriginSlidersToFields(optionsDialog, "x") + syncOriginSlidersToFields(optionsDialog) end }) :slider({ @@ -513,10 +503,13 @@ function showExportOptionsDialog() max = ORIGIN_SLIDER_STEPS, value = 0, onchange = function() - syncOriginSlidersToFields(optionsDialog, "y") + syncOriginSlidersToFields(optionsDialog) end }) - applyOriginMode(optionsDialog) + -- Set the initial state of the origin mode radio buttons based on cached options. + setOriginMode(optionsDialog, cachedOptions.originMode) + -- Set the initial state of the origin X and Y fields and sliders based on cached options. + setOriginXyValues(optionsDialog, cachedOptions.originX, cachedOptions.originY) -- button: Presets for common origin settings (center, bottom-center, bottom-left, top-left) optionsDialog:newrow() @@ -549,7 +542,7 @@ function showExportOptionsDialog() -- check: Option to round attachment coordinates to integers instead of keeping decimals optionsDialog:check({ id = "roundCoordinatesToInteger", - label = "Round Coordinates To Integer", + label = "Round To Integer", text = "Drop decimal pixels, May misalign pixels; not recommended for pixel art.", selected = cachedOptions.roundCoordinatesToInteger }) @@ -557,11 +550,16 @@ function showExportOptionsDialog() optionsDialog:separator({}) -- Override origin values from the first [origin] marker layer if present. + local markerOriginX, markerOriginY = getOriginFromMarkerLayer(activeSprite, getOriginMode()) + local markerOriginApplied = markerOriginX ~= nil and markerOriginY ~= nil if (markerOriginApplied) then - optionsDialog:modify({ id = "originX", text = string.format("%.3f", markerOriginX) }) - optionsDialog:modify({ id = "originY", text = string.format("%.3f", markerOriginY) }) - clampOriginXyFieldValue(optionsDialog) + setOriginXyValues(optionsDialog, markerOriginX, markerOriginY) end + -- Set the label to show whether the origin was set from the [origin] marker layer. + optionsDialog:modify({ + id = "originMarkerStatus", + text = markerOriginApplied and "✅ Origin set from [origin] marker layer." or "⚪ Origin not set from [origin] marker layer." + }) --#endregion --#region Image Settings @@ -803,29 +801,10 @@ ORIGIN_MODE = { NORMALIZED = "Normalized", -- Normalized origin coordinates in the range [0,1], where (0,0) is the bottom-left. PIXEL = "Pixel", -- Pixel-based origin coordinates, where (0,0) is the bottom-left of the sprite and values are in pixels. } -CURRENT_ORIGIN_MODE = ORIGIN_MODE.NORMALIZED +CURRENT_ORIGIN_MODE = nil ORIGIN_SLIDER_STEPS = 100 ORIGIN_SLIDER_IS_SYNCING = false ---[[ -Sets the selected origin mode radio button in the options dialog based on the given mode. -optionsDialog: The export options dialog instance -mode: The origin mode to select (ORIGIN_MODE.PIXEL or ORIGIN_MODE.NORMALIZED) -]] -function setOriginMode(optionsDialog, mode) - local currentMode = CURRENT_ORIGIN_MODE - - if (currentMode ~= mode) then - convertOriginCoordinatesByMode(optionsDialog, mode) - optionsDialog:modify({ id = "originModeNormalized", selected = mode == ORIGIN_MODE.NORMALIZED }) - optionsDialog:modify({ id = "originModePixel", selected = mode == ORIGIN_MODE.PIXEL }) - CURRENT_ORIGIN_MODE = mode - else - optionsDialog:modify({ id = "originModeNormalized", selected = mode == ORIGIN_MODE.NORMALIZED }) - optionsDialog:modify({ id = "originModePixel", selected = mode == ORIGIN_MODE.PIXEL }) - end -end - --[[ Gets the currently selected origin mode from the options dialog. optionsDialog: The export options dialog instance @@ -835,37 +814,26 @@ function getOriginMode() end --[[ -Applies the selected origin mode to the originX and originY fields. +Sets the selected origin mode radio button in the options dialog based on the given mode. optionsDialog: The export options dialog instance +mode: The origin mode to select (ORIGIN_MODE.PIXEL or ORIGIN_MODE.NORMALIZED) ]] -function applyOriginMode(optionsDialog) - local spriteWidth, spriteHeight = getActiveSpriteSize() - local mode = getOriginMode() - - local currentX = tonumber(optionsDialog.data.originX) - local currentY = tonumber(optionsDialog.data.originY) - if (mode == ORIGIN_MODE.PIXEL) then - if (currentX == nil) then - currentX = spriteWidth * 0.5 - end - if (currentY == nil) then - currentY = 0 - end - else - if (currentX == nil) then - currentX = 0.5 - end - if (currentY == nil) then - currentY = 0 - end +function setOriginMode(optionsDialog, mode) + -- If the mode is the same as the current mode, no need to update. + if (CURRENT_ORIGIN_MODE == mode) then + return end + CURRENT_ORIGIN_MODE = mode - optionsDialog:modify({ id = "originX", text = tostring(currentX) }) - optionsDialog:modify({ id = "originY", text = tostring(currentY) }) - clampOriginXyFieldValue(optionsDialog) + -- Update the selected state of the origin mode radio buttons based on the given mode. + optionsDialog:modify({ id = "originModeNormalized", selected = mode == ORIGIN_MODE.NORMALIZED }) + optionsDialog:modify({ id = "originModePixel", selected = mode == ORIGIN_MODE.PIXEL }) + -- When the origin mode changes, convert the current originX and originY values to the new mode. + convertOriginCoordinatesByMode(optionsDialog, mode) - -- Update the coordinate settings label to show the valid input range for the selected origin mode + -- Update the coordinate settings label to show the valid input range for the selected origin mode. if (mode == ORIGIN_MODE.PIXEL) then + local spriteWidth, spriteHeight = getActiveSpriteSize() optionsDialog:modify({ id = "coordinateSettings", text = string.format("Set Spine origin(0,0) in pixels. Range X:[0,%.0f], Y:[0,%.0f]", spriteWidth, spriteHeight) @@ -878,6 +846,18 @@ function applyOriginMode(optionsDialog) end end +--[[ +Set originX and originY field values. +optionsDialog: The export options dialog instance +x: The preset origin X value to set +y: The preset origin Y value to set +]] +function setOriginXyValues(optionsDialog, x, y) + optionsDialog:modify({ id = "originX", text = string.format(x) }) + optionsDialog:modify({ id = "originY", text = string.format(y) }) + clampOriginXyFieldValue(optionsDialog) +end + --[[ Converts current origin coordinates in the options dialog between normalized and pixel modes. optionsDialog: The export options dialog instance @@ -916,8 +896,7 @@ function convertOriginCoordinatesByMode(optionsDialog, toMode) convertedY = originY / spriteHeight end - optionsDialog:modify({ id = "originX", text = tostring(convertedX) }) - optionsDialog:modify({ id = "originY", text = tostring(convertedY) }) + setOriginXyValues(optionsDialog, convertedX, convertedY) end --[[ @@ -995,9 +974,7 @@ function setOriginPreset(optionsDialog, presetName) end end - optionsDialog:modify({ id = "originX", text = string.format("%.3f", x) }) - optionsDialog:modify({ id = "originY", text = string.format("%.3f", y) }) - clampOriginXyFieldValue(optionsDialog) + setOriginXyValues(optionsDialog, x, y) end --[[ @@ -1088,7 +1065,7 @@ function syncOriginSlidersFromFields(optionsDialog) end -- Syncs the originX and originY input fields from the current slider positions. -function syncOriginSlidersToFields(optionsDialog, axis) +function syncOriginSlidersToFields(optionsDialog) if (ORIGIN_SLIDER_IS_SYNCING) then return end @@ -1109,16 +1086,10 @@ function syncOriginSlidersToFields(optionsDialog, axis) local sliderY = tonumber(optionsDialog.data.originYSlider) or 0 ORIGIN_SLIDER_IS_SYNCING = true - if (axis == "x") then - local x = fromOriginSliderValue(sliderX, maxX) - optionsDialog:modify({ id = "originX", text = string.format("%.3f", x) }) - elseif (axis == "y") then - local y = fromOriginSliderValue(sliderY, maxY) - optionsDialog:modify({ id = "originY", text = string.format("%.3f", y) }) - end + local x = fromOriginSliderValue(sliderX, maxX) + local y = fromOriginSliderValue(sliderY, maxY) + setOriginXyValues(optionsDialog, x, y) ORIGIN_SLIDER_IS_SYNCING = false - - clampOriginXyFieldValue(optionsDialog) end -- Converts a coordinate value into slider step value based on current mode range. diff --git a/aseprite/README.md b/aseprite/README.md index a2f70d2..fcd5997 100644 --- a/aseprite/README.md +++ b/aseprite/README.md @@ -10,7 +10,7 @@ ___ ## Lua Script for importing Aseprite projects into Spine -## v1.2 +## v1.3 ### Installation @@ -35,23 +35,42 @@ After following these steps, the "Prepare-For-Spine" script should show up in th * Reset Config Button: Resets all options to their default values. * This will also clear any cached settings, so the next time you open the options dialog it will be restored to the default values. -* Origin (X/Y): Sets the coordinate origin for the exported images. - * This coordinate origin will align with the coordinate origin in Spine, affecting the default position of the images when imported into Spine. - * The origin coordinates are normalized to the range [0,1], where (0,0) represents the bottom-left corner of the image and (1,1) represents the top-right corner. - * There are also quick preset buttons for common origin configurations (Center, Bottom-Center, Bottom-Left, Top-Left) that will automatically set the X and Y values accordingly. -* Round Coordinates to Integer: When enabled, the script will round all coordinate values to the nearest integer, dropping any decimal part. - * This may cause pixel misalignment. For example, if the origin is set to center and the image has odd pixel dimensions, the true center lies at the center of the middle pixel rather than on an edge. Forcing integer coordinates can therefore introduce a half-pixel offset. - * Pixel art usually requires perfect pixel alignment, so this option is not recommended unless you have a specific need. -* Output Path: Allows you to specify a custom output path for the exported JSON file. - * By default, it will be saved in the same directory as your Aseprite project file. - * You can type a path directly into the text field, or click the button below to open a file picker dialog. After selecting a location, the path is filled into the text field automatically. -* Ignore Group Visibility: When enabled, the script will ignore the visibility of groups during export. - * This only considers each layer's own visibility and ignores the visibility of its parent group. That means a layer can still be exported even if its group is hidden, as long as the layer itself is visible. -* Clear Old Images: When enabled, the script will automatically delete any previously exported images in the output directory before exporting new ones. - * This helps to prevent confusion and clutter from old files that are no longer relevant to the current export. -* Export Button: Starts the export process with the configured options. - * After export completes, click the [Open File Folder] button to open the directory containing the exported files. -* Cancel Button: Closes the options dialog without exporting. + +* Coordinate Settings: Configure the coordinate origin used for exported images in Spine. + * Origin Mode: Sets how the origin is interpreted, with two modes: Normalized and Pixel. + * Normalized mode: Origin (X/Y) values are normalized to the [0,1] range. + * Pixel mode: Origin (X/Y) values represent exact pixel coordinates. + * [origin] tag import: If a layer name contains [origin], that layer is used as an automatic source for origin coordinates. + * The center point of that layer is converted to Origin (X, Y) in the export settings. + * Import success/failure is shown with an icon and status text. + * Origin (X/Y): Sets the coordinate origin for the exported images. + * This coordinate origin will align with the coordinate origin in Spine, affecting the default position of the images when imported into Spine. + * (0,0) represents the bottom-left corner of the image, and (1,1) or (image width, image height) represents the top-right corner. + * Sliders below the input fields let you quickly adjust X and Y for more intuitive origin placement. + * There are also quick preset buttons for common origin configurations (Center, Bottom-Center, Bottom-Left, Top-Left) that will automatically set the X and Y values accordingly. + * Round to Integer: When enabled, the script will round all coordinate values to the nearest integer, dropping any decimal part. + * This may cause pixel misalignment. For example, if the origin is set to center and the image has odd pixel dimensions, the true center lies at the center of the middle pixel rather than on an edge. Forcing integer coordinates can therefore introduce a half-pixel offset. + * Pixel art usually requires perfect pixel alignment, so this option is not recommended unless you have a specific need. + +* Image Settings: Control export image scale and padding. + * Scale(%): Adjusts exported image resolution as a percentage. The default is 100%, which means no scaling. + * Pixel art often appears too small on screen after export; increasing the scale can improve display size. + * Padding(px): Defines transparent pixel padding around image edges. The default is 1, meaning 1 pixel of edge padding. + * This can avoid aliasing artifacts for opaque pixels along the image edge. + +* Output Settings: Configure output paths for JSON and images, plus export behavior options. + * Output Path: Allows you to specify a custom output path for the exported JSON file. + * By default, it will be saved in the same directory as your Aseprite project file. + * You can type a path directly into the text field, or click the button below to open a file picker dialog. After selecting a location, the path is filled into the text field automatically. + * Ignore Hidden Layers: When enabled, the script ignores layer visibility during export. + * Layers are still exported even if the layer itself or its parent group is hidden. + * Clear Old Images: When enabled, the script will automatically delete any previously exported images in the output directory before exporting new ones. + * This helps to prevent confusion and clutter from old files that are no longer relevant to the current export. + +* Action Buttons: Start export using the current configuration. + * Export Button: Starts the export process with the configured options. + * After export completes, click the [Open File Folder] button to open the directory containing the exported files. + * Cancel Button: Closes the options dialog without exporting. #### 「Spine Import」 @@ -80,18 +99,28 @@ After following these steps, the "Prepare-For-Spine" script should show up in th ### Known Issues -#### v1.2 - * Opening the exported file location currently relies on `os` library APIs and may cause a brief UI stall (a few seconds). * Deleting old `images` files also relies on `os` library APIs and may cause a brief UI stall. * New layers added in Aseprite may have incorrect draw order when imported into an existing Spine skeleton, and need to be adjusted manually in Spine. -#### v1.1 +### Version History -* 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) -* 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. +#### v1.3 -### Version History +* Add coordinate modes and refine layer visibility options + * Added Normalized [0,1] and Pixel modes for origin coordinates. + * Added Sliders for Origin (X, Y) to allow more intuitive control. + * Added "Ignore Hidden Layers" toggle for more flexible exports. + * Removed redundant "Use layer visibility only" option. + +* Add Image Settings for scale and padding control + * Added Image Scale option to adjust the resolution of exported images. + * Added Image Padding setting to define pixel padding around image borders. + +* Support [origin] layer, add Spine logo, and refine UI layout + * Layers with [origin] in their name will be automatically configured as the origin coordinates in the export settings. + * Added Spine Logo to the dialog header for better branding/recognition. + * Refined UI Layout, Optimized spacing and alignment of all control panels for a cleaner look. #### v1.2 diff --git a/aseprite/README_cn.md b/aseprite/README_cn.md index 751b4b7..49e1814 100644 --- a/aseprite/README_cn.md +++ b/aseprite/README_cn.md @@ -10,7 +10,7 @@ ___ ## 用于将 Aseprite 项目导入 Spine 的 Lua 脚本 -## v1.2 +## v1.3 ### 安装 @@ -35,23 +35,42 @@ ___ * Reset Config 按钮:将所有选项 重置为 默认值。 * 同时会 清除缓存设置,因此下次 打开选项弹窗时会 恢复默认配置。 -* Origin (X/Y):设置导出图像在 Spine 中使用的 坐标原点。 - * 这个 坐标原点 会与 Spine中的坐标原点 对齐,影响导入后图片在Spine中的 默认位置。 - * 原点坐标 被规范化到 [0,1] 区间,其中 (0,0) 表示图像 左下角,(1,1) 表示图像 右上角。 - * 提供了 常用原点的 预设按钮(Center、Bottom-Center、Bottom-Left、Top-Left),点击后会 自动设置对应的 X、Y 值。 -* Round Coordinates to Integer:启用后,脚本会将所有 坐标值取整,丢弃小数部分。 - * 这可能导致 像素不对齐。例如,将原点设为中心 且 图片像素尺寸为奇数时,几何中心会落在 中间像素中心 而不是边界上,强制整数坐标 可能带来 半像素偏移。 - * 像素风格 通常需要严格的 像素对齐,除非有特殊需求,否则不建议开启该选项。 -* Output Path:允许你为导出的 JSON 文件 指定自定义输出路径。 - * 默认会保存到 Aseprite 项目文件所在目录。 - * 你可以直接在 文本框中输入路径,或者点击 下方按钮 打开文件选择对话框。选择后,路径会 自动填入文本框。 -* Ignore Group Visibility:启用后,导出时将 忽略组可见性。 - * 仅根据 图层自身可见性判断,不考虑其 父组是否可见。这意味着即使 组被隐藏,只要图层本身可见,仍会被导出。 -* Clear Old Images:启用后,导出前会 自动删除 输出目录中旧的图片。 - * 这可以减少 旧文件残留 造成的 混淆和目录杂乱。 -* Export 按钮:使用当前配置 开始导出。 - * 导出完成后,可点击 [Open File Folder] 按钮直接 打开导出目录。 -* Cancel 按钮:关闭选项弹窗并 取消导出。 + +* Coordinate Settings:坐标配置。设置导出图像在 Spine 中的坐标原点。 + * [origin]标签导入:如果图层名称中包含 [origin],该图层会被用作 原点坐标 的自动配置来源。 + * 该图层的 中心点坐标 会被转换为 导出设置中的 Origin (X, Y)。 + * 是否成功导入,会通过图标与文本进行提示。 + * Origin Mode:设置坐标原点的模式,支持 Normalized(归一化)和 Pixel(像素)两种模式。 + * Normalized 模式:Origin (X/Y) 的值被 规范化到 [0,1] 区间。 + * Pixel 模式:Origin (X/Y) 的值表示具体的 像素坐标。 + * Origin (X/Y):设置导出图像在 Spine 中使用的 坐标原点。 + * 这个 坐标原点 会与 Spine中的坐标原点 对齐,影响导入后图片在Spine中的 默认位置。 + * (0,0) 表示图像的左下角,(1,1) 或 (图像宽度, 图像高度) 表示图像的右上角。 + * 输入框底部的 滑块,可以快速调整 X 和 Y 的值,更直观地设置 坐标原点位置。 + * 提供了 常用原点的 预设按钮(Center、Bottom-Center、Bottom-Left、Top-Left),点击后会 自动设置对应的 X、Y 值。 + * Round to Integer:启用后,脚本会将所有 坐标值取整,丢弃小数部分。 + * 这可能导致 像素不对齐。例如,将原点设为中心 且 图片像素尺寸为奇数时,几何中心会落在 中间像素中心 而不是边界上,强制整数坐标 可能带来 半像素偏移。 + * 像素风格 通常需要严格的 像素对齐,除非有特殊需求,否则不建议开启该选项。 + +* Image Settings:控制导出图像的 缩放与边距。 + * Scale(%):调整导出 图像分辨率的 缩放比例 的百分比。默认值为 100%,表示不缩放。 + * 像素艺术在导出后,通常会有 在屏幕上显示的尺寸过小 的问题,可以通过增加 缩放比例 来放大显示的尺寸。 + * Padding(px):定义图像边缘的 像素留白。默认值为 1,表示在 图像边缘 留出1像素的空白区域。 + * 对于 不透明像素 沿图像边缘的 锯齿伪影,增加边距 可以起到 缓解作用。 + +* Output Settings:输出配置。Json文件 与 图片 的输出路径 与 各类导出选项。 + * Output Path:允许你为导出的 JSON文件 指定自定义 输出路径。 + * 默认会保存到 Aseprite 项目文件 所在目录。 + * 你可以直接在 文本框中 输入路径,或者点击 下方按钮 打开文件选择对话框。选择后,路径会 自动填入文本框。 + * Ignore Hidden Layers:启用后,脚本会在导出时 忽略图层的可见性。 + * 即使 图层 或其父组被隐藏,仍然会被导出。 + * Clear Old Images:启用后,导出前会 自动删除 输出目录中旧的图片。 + * 这可以减少 旧文件残留 造成的 混淆和目录杂乱。 + +* 执行按钮: 使用当前配置 开始导出。 + * Export 按钮:使用当前配置 开始导出。 + * 导出完成后,可点击 [Open File Folder] 按钮直接 打开导出目录。 + * Cancel 按钮:关闭选项弹窗并 取消导出。 #### 「Spine 导入」 @@ -80,18 +99,28 @@ ___ ### 已知问题 -#### v1.2 - * 打开 导出文件位置,目前依赖 `os` 库 API,可能导致短暂 UI 卡顿(几秒)。 * 删除旧的 `images` 文件,同样依赖 `os` 库 API,也可能导致短暂 UI 卡顿。 * Aseprite 中新增的图层,在导入到 Spine 的现有骨架时,可能会出现 绘制顺序不正确 的问题,需要在 Spine 中手动调整。 -#### v1.1 +### 版本历史 -* 隐藏图层组 不会阻止 组内图层导出。每个图层都需要 单独设置显示/隐藏(组可见性会被忽略)。 -* 选项数量相比 Photoshop 脚本更少。后续可能会补充,但作者目前很少用到这些选项。 +#### v1.3 -### 版本历史 +* 新增 坐标模式 并 优化图层可见性选项 + * 为原点坐标 新增 Normalized 与 Pixel 两种模式。 + * 为 Origin (X, Y) 新增滑杆,便于 更直观地调整。 + * 新增 "Ignore Hidden Layers" 开关,让导出更灵活。 + * 移除冗余的 "Use layer visibility only" 选项。 + +* 新增 Image Settings,用于控制 缩放与边距 + * 新增 Image Scale 选项,用于调整 导出图像分辨率的 缩放比例。 + * 新增 Image Padding 设置,用于定义 图像边缘的 像素留白。 + +* 支持 [origin] 图层、添加 Spine logo,并优化 UI 布局 + * 图层名称 中包含[origin]的图层,会被作为 原点坐标 自动配置到 导出设置。 + * 在对话框头部 新增 Spine Logo,提升识别度。 + * 优化 UI 布局,调整各控制面板的 间距与对齐,使界面更整洁。 #### v1.2 From 12748ee1267ef8790588440328479d4ae995a8e1 Mon Sep 17 00:00:00 2001 From: Ale Date: Thu, 19 Mar 2026 21:41:33 +0900 Subject: [PATCH 15/25] [Aseprite] Update the logo image of Spine --- aseprite/Images/Prepare-For-Spine-Logo.png | Bin 485 -> 0 bytes aseprite/Images/Spine-Logo.png | Bin 0 -> 486 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 aseprite/Images/Prepare-For-Spine-Logo.png create mode 100644 aseprite/Images/Spine-Logo.png diff --git a/aseprite/Images/Prepare-For-Spine-Logo.png b/aseprite/Images/Prepare-For-Spine-Logo.png deleted file mode 100644 index f76a0ed59ce9f76b73785b1b145853dd52da1bc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 485 zcmVPx$pGibPRA_tes{gAAQ1j0 zkV?HnJVN-vWgX`}F?x8tU39@@BmuqaqN(}B=pR=baaX(*Z+pMu_I;ScrM5^gAsiHq z!lID32-Q7dui(j5#%28~3)9Hn8>Ug!t`<11BW#wAtF<}naa2cKc}=^$P?Ux$HO8L3 z+&ytwvvs7$!JDD#5+iUs*d{J(_9;SfN;V4Yd9aKdH#b*Yhhf(^+)ZN^vqP6l>~nR8y;eFp z-%Flnb(0Y7s#joDu*5Ci$63Uz$Kg%Fng^PAb)LiWI@gZ}3+Ts#74(yQC4WxxAzeTk b{NIsZ(Vy~+kQ4s$00000NkvXXu0mjf8yMV) diff --git a/aseprite/Images/Spine-Logo.png b/aseprite/Images/Spine-Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d16a47cbaf2398d393db389f0be19b4e7c40a872 GIT binary patch literal 486 zcmV@P)Px$ph-kQRA_tes|tg5eR=1 zh*Iwmj}U(FwT^S27(G1SPP$++l7QZI(bRlm^pC5JxGUa@x4mC+`#sFzQd=aL5Dtn) zVNu9igzBEKSMcNtUfJuNK&^BW#xTtF<}naa2Rx^_qHnp(qVis*gQ; zxqITWX3I#AgEvFfB}U+Suufdo>{Eo|lq?k3^WZhE-`rSn8HSzXa6@4lMXnF;DGAYG z4%?xDvbVIZW5VC<38(Qei2-}yML@GClsur4@g>Mmg%dm#0%wp!qQq;Fj%Y>9dK?w+ zHBacj!KNeH3BHD54rFMy@V1iiu)^Vn&-S<^QmgQ+;qW(y(p(DCO0*h>AcdpmGfm?- zw6UQQ-mkbFEzl;xhrM#aa;me^l*FAJx>N=sf%LfCAfR?v6ymHgSshjamH c@P9{s0ghktjIM@ENdN!<07*qoM6N<$g0pzz9{>OV literal 0 HcmV?d00001 From 492392917fac1102c3df4e6294f02af5728eed16 Mon Sep 17 00:00:00 2001 From: Ale Date: Thu, 19 Mar 2026 21:46:10 +0900 Subject: [PATCH 16/25] [Aseprite] Update the UI images in the README --- aseprite/Images/image-1.png | Bin 16069 -> 16081 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/aseprite/Images/image-1.png b/aseprite/Images/image-1.png index c57e4c9dec29de118841ed55bc9e5bba6fde11a6..f95fb470f7ec1ca1d64b47e3b2e545bce6a9637e 100644 GIT binary patch literal 16081 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 literal 16069 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 k_6j8MmNp2KDNyT8R@Tkn$|U>RWz>% From 84b6ad2d3f38e5abba5362d97948cc3ed04d4307 Mon Sep 17 00:00:00 2001 From: Ale Date: Thu, 19 Mar 2026 22:11:32 +0900 Subject: [PATCH 17/25] [Aseprite] Update the content of the README --- aseprite/README.md | 2 +- aseprite/README_cn.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aseprite/README.md b/aseprite/README.md index fcd5997..c5d512b 100644 --- a/aseprite/README.md +++ b/aseprite/README.md @@ -92,7 +92,7 @@ After following these steps, the "Prepare-For-Spine" script should show up in th * Create a new skeleton: If checked, a new skeleton will be created during import. * If you already created an empty new project, you do not need to check this option and can import directly. * Import into an existing skeleton: If checked, imported assets will be added to an existing skeleton. - * Replace existing attachments: If checked, attachments with the same name in the existing skeleton will be replaced during import. + * Replace existing attachments: It is recommended to select this option to ensure that attachments are correctly replaced and coordinates and other related properties are updated. * New layers will generate new attachments and be added to the existing skeleton, but the draw order may be incorrect and needs to be manually adjusted in Spine. * Import button: Start importing with the current configuration. * Cancel button: Close the dialog and cancel the import. diff --git a/aseprite/README_cn.md b/aseprite/README_cn.md index 49e1814..1cd4f13 100644 --- a/aseprite/README_cn.md +++ b/aseprite/README_cn.md @@ -92,7 +92,7 @@ ___ * Create a new skeleton:如果选中,导入时会 创建一个新的骨架。 * 如果已经创建了 空的新项目,则不需要选中该选项,直接导入即可。 * Import into an existing skeleton:如果选中,导入的资源会被添加到 现有骨架中。 - * Replace existing attachments:如果选中,导入时会 替换现有骨架中 同名的附件。 + * Replace existing attachments:建议选中,以确保 附件被正确替换,更新 坐标和其他相关属性。 * 新增的图层 会生成 新的附件 并添加到 现有的骨架中,但是 绘制顺序 可能会出现问题,需要在 Spine 中手动调整。 * Import 按钮:使用当前配置 开始导入。 * Cancel 按钮:关闭对话框并 取消导入。 From 22ceb83ac9027a02ddcd01c05bd785e1b93f302f Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Wed, 15 Apr 2026 10:15:53 +0200 Subject: [PATCH 18/25] [Aseprite] Replace os.execute shell calls with pure Lua filesystem APIs --- aseprite/Prepare-For-Spine.lua | 36 ++++++++-------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index df6b7b9..6a06f70 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -279,31 +279,19 @@ function deleteDirectoryRecursive(path) if (path == nil or path == "") then return end - - if (app.fs.pathSeparator == "\\") then - os.execute('rmdir /S /Q "' .. path .. '"') - else - os.execute('rm -rf "' .. path .. '"') - end -end - ---[[ -Opens the OS file explorer and selects the exported file when possible. -filePath: The full path of the exported file -]] -function openFileLocation(filePath) - if (filePath == nil or filePath == "") then + if (not app.fs.isDirectory(path)) then return end - if (app.fs.pathSeparator == "\\") then - os.execute('explorer /select,"' .. filePath .. '"') - else - local dirPath = app.fs.filePath(filePath) - if (app.fs.pathSeparator == "/") then - os.execute('xdg-open "' .. dirPath .. '"') + for _, name in ipairs(app.fs.listFiles(path)) do + local fullPath = app.fs.joinPath(path, name) + if (app.fs.isDirectory(fullPath)) then + deleteDirectoryRecursive(fullPath) + else + os.remove(fullPath) end end + os.remove(path) end --#region Layer Marker Functions @@ -775,14 +763,6 @@ function showExportCompletedDialog(jsonFileName, failedPaths) end completedDialog:newrow() - -- Button to open the file location in the OS file explorer. - completedDialog:button({ - text = "Open File Folder", - onclick = function() - openFileLocation(jsonFileName) - completedDialog:close() - end - }) -- Button to close the dialog. completedDialog:button({ text = "OK", From 5640bd83281e046e777fbf46b6470725ee86c5c6 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Wed, 15 Apr 2026 10:22:46 +0200 Subject: [PATCH 19/25] [Aseprite] Embed Spine logo as RLE data, add regeneration tool --- aseprite/Prepare-For-Spine.lua | 153 +++++++++++++-------------- aseprite/tools/generate_logo_rle.lua | 111 +++++++++++++++++++ 2 files changed, 183 insertions(+), 81 deletions(-) create mode 100644 aseprite/tools/generate_logo_rle.lua diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 6a06f70..ddb073a 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -294,6 +294,8 @@ function deleteDirectoryRecursive(path) os.remove(path) end + + --#region Layer Marker Functions --[[ @@ -1293,94 +1295,83 @@ function DrawSpineLogo(optionsDialog) return end - -- load the logo image from cache. - local cacheDir = app.fs.joinPath(app.fs.filePath(app.fs.userConfigPath), "Cache") - app.fs.makeDirectory(cacheDir) - local logoPath = app.fs.joinPath(cacheDir, "Prepare-For-Spine-Logo.png") - local loadedImage = nil - local cacheFile = io.open(logoPath, "rb") - local hasCachedLogo = cacheFile ~= nil - if hasCachedLogo then - cacheFile:close() - local loadedOk, imageOrError = pcall(function() - return Image({ fromFile = logoPath }) - end) - if (loadedOk == true and imageOrError ~= nil) then - loadedImage = imageOrError - end - end - - -- if loading from cache failed, attempt to download the logo image and save it to cache, then load it. - if (loadedImage == nil) then - local logoUrl = "https://github.com/EsotericSoftware/spine-scripts/blob/master/aseprite/Images/Spine-Logo.png" - local downloadOk = false - if (app.fs.pathSeparator == "\\") then - local cmd = 'powershell -NoProfile -Command "try { Invoke-WebRequest -Uri \"' .. logoUrl .. '\" -OutFile \"' .. logoPath .. '\" -UseBasicParsing; exit 0 } catch { exit 1 }"' - local result = os.execute(cmd) - downloadOk = result == true or result == 0 - else - local cmd = 'curl -L -o "' .. logoPath .. '" "' .. logoUrl .. '"' - local result = os.execute(cmd) - downloadOk = result == true or result == 0 - end + -- Decode the embedded Spine logo (78x29, 4 colors, RLE-encoded). + -- To regenerate after changing Images/Spine-Logo.png: + -- 1. Run tools/generate_logo_rle.lua in Aseprite (File > Scripts) + -- 2. Paste the contents of tools/logo_rle_output.txt below. + local logoWidth = 78 + local logoHeight = 29 + local rleData = "40,70D,60,74D,30,76D,20,76D,0,34D,3R,74D,8R,70D,9R,43D,5W,6D,2W,D,5W,7D,9R,6D,2W,2D,4W,10D,5W,13D,7W,5D,9W,7D,7R,7D,2W,D,6W,8D,8W,10D,3W,3D,2W,5D,4W,2D,4W,9D,3R,8D,4W,3D,3W,6D,3W,4D,2W,10D,2W,11D,3W,4D,3W,20D,3W,5D,2W,6D,2W,6D,2W,9D,3W,10D,2W,6D,2W,7D,R,3D,R,8D,2W,6D,2W,5D,3W,6D,2W,10D,4W,8D,2W,6D,2W,6D,7R,7D,2W,6D,2W,5D,11W,11D,5W,6D,2W,6D,2W,6D,7R,7D,2W,6D,2W,5D,11W,13D,4W,5D,2W,6D,2W,7D,5R,8D,2W,6D,2W,5D,2W,24D,2W,5D,2W,6D,2W,9D,R,10D,2W,6D,2W,5D,2W,24D,2W,5D,3W,4D,3W,20D,2W,6D,2W,6D,2W,17D,2W,3D,3W,5D,4W,2D,3W,11D,3R,7D,2W,6D,2W,6D,3W,5D,2W,9D,7W,6D,2W,D,5W,10D,6R,6D,2W,6D,2W,7D,9W,10D,5W,7D,2W,2D,3W,12D,4R,7D,2W,6D,2W,9D,5W,24D,2W,18D,2R,56D,2W,76D,2W,76D,2W,18D,4R,75D,2R,37D,0,76D,20,76D,30,74D,60,70D,40" + local colorMap = { + ["0"] = app.pixelColor.rgba(0, 0, 0, 0), + ["R"] = app.pixelColor.rgba(255, 64, 0, 255), + ["W"] = app.pixelColor.rgba(240, 240, 241, 255), + ["D"] = app.pixelColor.rgba(2, 18, 18, 255), + } - if (downloadOk == true) then - local loadedOk, imageOrError = pcall(function() - return Image({ fromFile = logoPath }) - end) - if (loadedOk == true and imageOrError ~= nil) then - loadedImage = imageOrError + local buildOk, logoImage = pcall(function() + local img = Image(logoWidth, logoHeight, ColorMode.RGB) + local px = 0 + for token in string.gmatch(rleData, "[^,]+") do + local count, ch = string.match(token, "^(%d+)(.)$") + if (count == nil) then + ch = token + count = 1 + else + count = tonumber(count) + end + local color = colorMap[ch] + for _ = 1, count do + local x = px % logoWidth + local y = math.floor(px / logoWidth) + img:drawPixel(x, y, color) + px = px + 1 end end - end + return img + end) - -- Draw the logo image on a canvas in the options dialog, or show an error message if loading failed. - if (loadedImage == nil) then - optionsDialog:label({ - id = "spineLogoStatus", - text = "Logo render failed (download/cache load)." - }) - optionsDialog:newrow() + if (not buildOk or logoImage == nil) then return - else - local displayScale = 2 - local displayImage = loadedImage - -- Build a 2x nearest-neighbor display image for clearer pixel-art rendering in the dialog. - local scaledOk, scaledOrError = pcall(function() - local scaled = Image(loadedImage.width * displayScale, loadedImage.height * displayScale, loadedImage.colorMode) - for y = 0, loadedImage.height - 1 do - for x = 0, loadedImage.width - 1 do - local px = loadedImage:getPixel(x, y) - local sx = x * displayScale - local sy = y * displayScale - scaled:putPixel(sx, sy, px) - scaled:putPixel(sx + 1, sy, px) - scaled:putPixel(sx, sy + 1, px) - scaled:putPixel(sx + 1, sy + 1, px) - end - end - return scaled - end) - if (scaledOk == true and scaledOrError ~= nil) then - displayImage = scaledOrError - end + end - local minCanvasWidth = 360 - local canvasWidth = math.max(displayImage.width, minCanvasWidth) - local canvasHeight = displayImage.height - local drawX = math.floor((canvasWidth - displayImage.width) * 0.5) - local drawY = 0 - optionsDialog:canvas({ - id = "spineLogoCanvas", - width = canvasWidth, - height = canvasHeight, - onpaint = function(ev) - local gc = ev.context - gc.antialias = false - gc:drawImage(displayImage, drawX, drawY) + -- Build a 2x nearest-neighbor display image for clearer rendering in the dialog. + local displayScale = 2 + local displayImage = logoImage + local scaledOk, scaledOrError = pcall(function() + local scaled = Image(logoWidth * displayScale, logoHeight * displayScale, ColorMode.RGB) + for y = 0, logoHeight - 1 do + for x = 0, logoWidth - 1 do + local px = logoImage:getPixel(x, y) + local sx = x * displayScale + local sy = y * displayScale + scaled:drawPixel(sx, sy, px) + scaled:drawPixel(sx + 1, sy, px) + scaled:drawPixel(sx, sy + 1, px) + scaled:drawPixel(sx + 1, sy + 1, px) end - }) - end + end + return scaled + end) + if (scaledOk == true and scaledOrError ~= nil) then + displayImage = scaledOrError + end + + local minCanvasWidth = 360 + local canvasWidth = math.max(displayImage.width, minCanvasWidth) + local canvasHeight = displayImage.height + local drawX = math.floor((canvasWidth - displayImage.width) * 0.5) + local drawY = 0 + optionsDialog:canvas({ + id = "spineLogoCanvas", + width = canvasWidth, + height = canvasHeight, + onpaint = function(ev) + local gc = ev.context + gc.antialias = false + gc:drawImage(displayImage, drawX, drawY) + end + }) optionsDialog:separator({}) end diff --git a/aseprite/tools/generate_logo_rle.lua b/aseprite/tools/generate_logo_rle.lua new file mode 100644 index 0000000..b6dd319 --- /dev/null +++ b/aseprite/tools/generate_logo_rle.lua @@ -0,0 +1,111 @@ +--[[ +Generates RLE-encoded pixel data from the Spine logo PNG for embedding +in Prepare-For-Spine.lua. + +How to use: + 1. Open Aseprite + 2. Run this script (File > Scripts > generate_logo_rle) + 3. The RLE string is saved to a text file next to this script + 4. Copy the output into DrawSpineLogo() in Prepare-For-Spine.lua + +The logo PNG is expected at ../Images/Spine-Logo.png relative to this script. +]] + +-- Resolve path to the logo image +local scriptDir = app.fs.filePath(debug.getinfo(1, "S").source:sub(2)) +local logoPath = app.fs.joinPath(scriptDir, app.fs.joinPath("..", app.fs.joinPath("Images", "Spine-Logo.png"))) +local outputPath = app.fs.joinPath(scriptDir, "logo_rle_output.txt") + +-- Known color palette: RGBA -> single character +local colorChars = { + ["0,0,0,0"] = "0", + ["255,64,0,255"] = "R", + ["240,240,241,255"] = "W", + ["2,18,18,255"] = "D", +} + +-- Load the logo as a sprite +local logoSprite = Sprite({ fromFile = logoPath }) +if (logoSprite == nil) then + app.alert("Error: Could not load " .. logoPath) + return +end + +local img = logoSprite.cels[1].image +local w = img.width +local h = img.height + +-- Read pixels and map to characters +local chars = {} +local unknown = {} +for y = 0, h - 1 do + for x = 0, w - 1 do + local px = img:getPixel(x, y) + local r = app.pixelColor.rgbaR(px) + local g = app.pixelColor.rgbaG(px) + local b = app.pixelColor.rgbaB(px) + local a = app.pixelColor.rgbaA(px) + local key = r .. "," .. g .. "," .. b .. "," .. a + local ch = colorChars[key] + if (ch == nil) then + unknown[key] = true + ch = "?" + end + chars[#chars + 1] = ch + end +end + +-- Close the sprite we opened (don't leave it in the editor) +logoSprite:close() + +-- Check for unknown colors +local unknownList = {} +for key, _ in pairs(unknown) do + unknownList[#unknownList + 1] = key +end +if (#unknownList > 0) then + local msg = "Error: Image contains unsupported colors:\n" + for _, c in ipairs(unknownList) do + msg = msg .. " RGBA(" .. c .. ")\n" + end + msg = msg .. "\nUpdate colorChars in this script and colorMap in the Lua script." + app.alert(msg) + return +end + +-- RLE encode +local rleTokens = {} +local i = 1 +while i <= #chars do + local c = chars[i] + local count = 1 + while i + count <= #chars and chars[i + count] == c do + count = count + 1 + end + if (count == 1) then + rleTokens[#rleTokens + 1] = c + else + rleTokens[#rleTokens + 1] = tostring(count) .. c + end + i = i + count +end + +local rleString = table.concat(rleTokens, ",") + +-- Build output text +local output = string.format( + ' local logoWidth = %d\n local logoHeight = %d\n local rleData = "%s"', + w, h, rleString +) + +-- Write to file +local f = io.open(outputPath, "w") +if (f == nil) then + app.alert("Error: Could not write to " .. outputPath) + return +end +f:write("-- Paste the following into DrawSpineLogo() in Prepare-For-Spine.lua\n\n") +f:write(output .. "\n") +f:close() + +app.alert("Done! RLE output saved to:\n" .. outputPath .. "\n\nSize: " .. w .. "x" .. h .. " (" .. #chars .. " pixels)\nRLE length: " .. #rleString .. " chars") From 3978fba04a258f113f86f1260e85ae7d0a1d3e2c Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Wed, 15 Apr 2026 10:23:09 +0200 Subject: [PATCH 20/25] [Aseprite] Fix rounding: use math.floor instead of math.modf (truncation) --- aseprite/Prepare-For-Spine.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index ddb073a..58a57c5 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -227,10 +227,10 @@ function captureLayers( attachmentX = attachmentX * scaleFactor attachmentY = attachmentY * scaleFactor slotsJson[index] = string.format([[ { "name": "%s", "bone": "%s", "attachment": "%s" } ]], name, "root", name) - -- If roundCoordinatesToInteger is true, round the attachmentX and attachmentY to the nearest integer using math.modf. Otherwise, keep the decimal values with 3 decimal places. + -- If roundCoordinatesToInteger is true, round the attachmentX and attachmentY to the nearest integer. Otherwise, keep the decimal values with 3 decimal places. if (roundCoordinatesToInteger == true) then - attachmentX = math.modf(attachmentX) - attachmentY = math.modf(attachmentY) + attachmentX = math.floor(attachmentX + 0.5) + attachmentY = math.floor(attachmentY + 0.5) skinsJson[index] = string.format([[ "%s": { "%s": { "x": %d, "y": %d, "width": 1, "height": 1 } } ]], name, name, attachmentX, attachmentY) else skinsJson[index] = string.format([[ "%s": { "%s": { "x": %.3f, "y": %.3f, "width": 1, "height": 1 } } ]], name, name, attachmentX, attachmentY) From 75d70051726225dca67f389175cba419771db6ba Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Wed, 15 Apr 2026 10:25:49 +0200 Subject: [PATCH 21/25] [Aseprite] Fix crash when exporting layers with no cels (empty layers) --- aseprite/Prepare-For-Spine.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 58a57c5..3de54a1 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -194,9 +194,13 @@ function captureLayers( for i, layer in ipairs(layers) do -- Ignore groups and non-visible layers if (not layer.isGroup and effectiveVisibilities[i] == true and not isMarkerLayer(layer)) then + -- Skip layers with no cels (empty layers) + local cel = layer.cels[1] + if (cel == nil) then + goto continue + end -- Set the layer to visible so we can capture it, then set it back to hidden after layer.isVisible = true - local cel = layer.cels[1] local imagePath = imagesDir .. separator .. layer.name .. ".png" local savedOk = false savedOk = pcall(function() @@ -236,6 +240,7 @@ function captureLayers( skinsJson[index] = string.format([[ "%s": { "%s": { "x": %.3f, "y": %.3f, "width": 1, "height": 1 } } ]], name, name, attachmentX, attachmentY) end index = index + 1 + ::continue:: end end From 12120f3a08b7bee5a878befb5f638183fbb0d87b Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Wed, 15 Apr 2026 10:32:03 +0200 Subject: [PATCH 22/25] [Aseprite] Fix PNG layers warning by flattening cloned sprite before save --- aseprite/Prepare-For-Spine.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 3de54a1..69a4eb3 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -217,6 +217,7 @@ function captureLayers( cropped:resize(scaledWidth, scaledHeight) end + cropped:flatten() cropped:saveCopyAs(imagePath) cropped:close() end) From b224519a63c30886d5d38740bc6ead540f5fa3db Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Wed, 15 Apr 2026 10:44:05 +0200 Subject: [PATCH 23/25] [Aseprite] Rename config cache extension from .json to .txt (not JSON format) --- aseprite/Prepare-For-Spine.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index 69a4eb3..975be74 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -1237,7 +1237,7 @@ function loadCachedOptions(defaultOutputPath) -- Create a config directory under the user's Aseprite config path, and define the config file path local configDir = app.fs.joinPath(app.fs.filePath(app.fs.userConfigPath), "Cache") app.fs.makeDirectory(configDir) - local configPath = app.fs.joinPath(configDir, "Prepare-For-Spine-Config.json") + local configPath = app.fs.joinPath(configDir, "Prepare-For-Spine-Config.txt") local configFile = io.open(configPath, "r") if (configFile == nil) then return cached, configPath From 15d98b128a241dabe797f63bf74663adb62eb5c1 Mon Sep 17 00:00:00 2001 From: Ale Date: Thu, 16 Apr 2026 15:32:01 +0900 Subject: [PATCH 24/25] [Aseprite] Update README to v1.3.1 - Update the document content to v1.3.1 - Optimize UI layout and display. --- aseprite/Images/Spine-Logo.png | Bin 486 -> 2163 bytes aseprite/Images/image-1.png | Bin 16081 -> 16410 bytes aseprite/Images/image-3.png | Bin 0 -> 86635 bytes aseprite/Images/image-4.png | Bin 0 -> 44376 bytes aseprite/Images/image-5.png | Bin 0 -> 67799 bytes aseprite/Prepare-For-Spine.lua | 26 +++++++++++----------- aseprite/README.md | 38 +++++++++++++++++++++++++++++++-- aseprite/README_cn.md | 38 +++++++++++++++++++++++++++++++-- 8 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 aseprite/Images/image-3.png create mode 100644 aseprite/Images/image-4.png create mode 100644 aseprite/Images/image-5.png diff --git a/aseprite/Images/Spine-Logo.png b/aseprite/Images/Spine-Logo.png index d16a47cbaf2398d393db389f0be19b4e7c40a872..3f4335ed57812a7bee6857370ed91432934e54e4 100644 GIT binary patch literal 2163 zcmeAS@N?(olHy`uVBq!ia0y~yVCG?9U{vB@1B!&Wg^Bd{0baag-RYz`wsg8bs{3waViJ5^{#lvG;veZShyaO2?Z)1@cR*Vfri`xC<;*DMVT zO$AOOiO4FpAM&fk=9DLPNUU7J$-Zjw8r_X$L|;b zUF1n5@$yrN%=^{+h2Fb$1kY_dy=me9_qlIA?K$)9TTyInOK?@4ZJ7MSDLR+$w_RVo zzH-a+)NQfz?9GWx7~r(?Yu_)1&1<5RVzxd=ul#6twk|HrmVL2(`SjM)fvd%O!*zY4 z3+$$cp1!#G>_6K*akXzQ+L`x$`uQrEqy(|$?4{2+>G4W7zkkY|UG@L?i}PD!cA9se z{?oO2e|<&lZvXW9b#7gS2xY-VO_7D%KBe*nNXkdH76-# zZ>7Dgyt2ymMc}l>j0_gSBmbX8x+w*VV zfxn-AHsph2;5?|jAtr}JR_&V@J8f~}`q_IY*zA(YohjLH{~<}m!L)e~g%@|<+B|(v z`evSe=G^BF_%D9%zW6=&?mhgUOi4};Z&YuGcDIUEzQ4Rm^-SpbZrAg_zW|f$wa>GPXqkui3^iyqtr=0Lak(>L3P&zoKxx%G_A?HBJ81*P)R zzx}?wV)K6fPlhB#2q-eICOy;=t2__P$n&2+wd>WBpI<9@tI7Dz`kU|N?b`3ElVsp7 z8`@P)Px$ph-kQRA_tes|tg5eR=1 zh*Iwmj}U(FwT^S27(G1SPP$++l7QZI(bRlm^pC5JxGUa@x4mC+`#sFzQd=aL5Dtn) zVNu9igzBEKSMcNtUfJuNK&^BW#xTtF<}naa2Rx^_qHnp(qVis*gQ; zxqITWX3I#AgEvFfB}U+Suufdo>{Eo|lq?k3^WZhE-`rSn8HSzXa6@4lMXnF;DGAYG z4%?xDvbVIZW5VC<38(Qei2-}yML@GClsur4@g>Mmg%dm#0%wp!qQq;Fj%Y>9dK?w+ zHBacj!KNeH3BHD54rFMy@V1iiu)^Vn&-S<^QmgQ+;qW(y(p(DCO0*h>AcdpmGfm?- zw6UQQ-mkbFEzl;xhrM#aa;me^l*FAJx>N=sf%LfCAfR?v6ymHgSshjamH c@P9{s0ghktjIM@ENdN!<07*qoM6N<$g0pzz9{>OV diff --git a/aseprite/Images/image-1.png b/aseprite/Images/image-1.png index f95fb470f7ec1ca1d64b47e3b2e545bce6a9637e..77920ae7acfa31e10c25676f198c079933b17da7 100644 GIT binary patch literal 16410 zcmcJ$30#ut`ZxZx%(UlBooREM_M9Rs$IRSjveGoQbaG!RCrwc)a0wBxnaY}6S`>0+ z%+wmU%!LpIr$sXbOjA?@T14D{0+j%P_W`u&%;|s5@BIGn`}vqY!2{2IU;FjFzW0?Q zt`18Uu2~2Gz>-6b``rOx&P(uL-u&6%E0HmbUhvN>guBB&pqy?n3;^E(hxY&Y)43c0 zJE#=GL>o`mWI(_F(I|Y;tT*Fs3m4A%<@dS;bBE^J%-gj-u;2*7sN;gWv0u+``UNMq z)_yVEE!em~9Qdu{-J~NX7r#s?{T*Fe2t>gb7Bm(*EtLLVx9`fp$e&*e;ofrQ z(X6CuT&(emv%4sdef-U!gBDd%Rf5quy%0vJ4=P0P8itkjRe5Mu3M2+UXjP9%73m#J zV>C>l0)WP&$jPyL=^ar-XA|^k0Udnh-op%aOQw{}gq5+40bq~s4Hy8-heHKR!8Zp5 zcIobQYGZW-ZzN7V;^pBKCN0>EbEeD!zP&u44*<45(CdwD6K=WhwX-uVMWWHU?sX3z zI-0Q2Z@0Qxe<%AANc`6LS^`(`h|h_WIm4!Cr{o?yj>OKRH>rN`+GOYp7?tA1vc6>e zE;(L;0{~YCdcDMuj9AFY=oC&VEbTXN0pP1U;dJSdgMO}V5^+|!#N!9XjvGz zuT3%=*yBG4zTa3xd$$O0j-a?9mcI9A#E;cFV^_=q&ODb}##yLi37`mjF1(1dYIzK2T=JxWil*uwt zQ97pqGuqNDE$fnn(XDO!LK_q>`fLEe`%fibz0YY_$R^i}a;XIOIz^M0yOXgMc2nVa zrdz^(@e%+yYr91w`kPW^9OsaUG9{=ym-!AsP-dogxT#*c9Rl@t+|aC_4{9)#<%SWv z6{lmO2IM9-GPI?36-XF8j`U)I3G(q!rf{}FJylUct&d%K{Cn;9>h93qypykg{KvuA$M8#m zL4&_k>C|-lgT`Wh)0p5%xInlL5M z^XE&I4SGrA7h@jsE&SGN?NR&fx=(|;Jo8idAd*-LOWo7HPqVon|DiFs{qHrGZL%*D zzpg_(p{c)Yy7BjIA}F(Vj}WPfh87m)!+JMJUs9C9Z-}2?Vo5h2)$^v7!cc!)B>}cO5aVb!Sk#^UvF->qQe5TuJ$5kl4>er`i4;hBtvasQqylyzF)F#mF+d4d%U?4in&AM<%K=$>CyA?1bb6bDjE<{mj0A zeKlz znJNd&R+=0YB?$bpEee$TYCe<6gvQN#HOKMc>mxY?0{10@gcClmaVWPJ@6~=3jBr3i zYPEi9#)gWs=GV0a@R-M6_ji4 z@xlxRG;S`i=R2v0OTG7fxXv2}vTwc{=bdsIrlk*XfyQL8CfS;z3tj7EgBPMDFd#8@ zA~Q~B+TfGuJ#lvd00GMVpOfk*`Sm|tUh^~G+JKIUkw`o7c%1jiD;sBiwJdiQPuD~L zDzjNY(k}LUAUa*uSvZu+>2tz{t`l=@q!}w^3v%%v^l!-A5$=F9q zvPX?k-AAN;T=k?|aCj4fCR>AQONw{rgpchM5q*S6q^Z5%5DPb+0IBRBKP9j&Oj4LDAsrPim-B9)3Y(*s|mcVoOXUdC+;Ow_WfJ%Y0lIY zC(8vqMl%i-2#;EttCfGUBW0BDn}M%5H8xdWS+wzoU|RM*$cc#Z<#y#KQ%@XzLw>TF z>$?lRaB%NnXVc>zpIUprNWQ$YhBlgjQU%zVp#uH(w4NE##_yl^i*3e*31i~7W)L5r zHy;XXThvzUVC92dQZHsCp7Z*{C+MG9y!HSy0r5 ziulwt?Ybg{qBiqZH}I?+U-k#x#-t%8>eZ)dh}G_SqyE?gIr(*zu)_;ti}p}%DeQG@lM0o4`_9$(=ee(4coWK4riP0 zF1NspCwVv#6t9hgeoq4$-(S=Ebc@C25s^q0<}|z#Gv;SfXk3U6hBA%iRCgzJpGvPN z9V2&8KR=9~3#6vZ#H6EYg>YWg&zt}?OE}M@kdRk-0C@HAV}z-J_N%z6Ux^-0>QOtz zM0!VsS}1DEE0_Z`J4{FZ9er5&TY{5G!{qDM^bYK}9u?;7$X)toAyMwTi{=y%f?L6SaqR*do3qQS@6BqdEr^T>cBC6`wq~*Irm>bLh z+i_{Tr;qf7klX1#30Ls8)ov;qt1ufc9YyWBqmp$SzkY^z$#(9oF4#F~xl}+@C3IC= z-YMdJ2%PmX$U0)jyWQ@j2ddr`gB#)5$*oKZvF{#Z#ZUH{sy^hVcWgCQ5#61z0%K~f zyZkA3oVap?Q5|t^=mmV8ca=0Na6;N8HYgTUhA3}QAn4Rf`;8h%^jH3jsAnAwvsXD! zOX3~JkgBHHy=(SxGFIbNUFjV>mFZP14+1Xq)Wspa5r^sx3xrYlK^(HD0#@Up*Qf9(>3h11FKF$yD4E5U_YQDs4e>6nSMsd*NeQhln#j&TF2$3r04 zvJ=~RWDlnGUa#lgoVW17J`tRMHvqNUF1lEkwxKo?Ce?&<5SShF{)njATXw~Gn@+tq% z=5W&bUe1Fme@5l(S`%iEY)u9nsi8kFum>Wm zq871vN(UCIqk|7$BycF8sC`f^O7Kx!`qJ>66Kf4QDJVqN%7{@^d4L* z3nz8FN~|b%cVaY%pGwi=G_N2>6B{&gHOCZqbxT|SJbN-gCLA;^rsDJW*AKqP8gGDY zxl%EFBCe$;w0T)wrW8YUGYJR^va6&&eR@Ds3oMjLCm8{fELmw-nx|k^YLp8WSB?~(gA|t1%U(*U0>QFUjUaeVl?rKGD ziSP!d^@l}TU4=POh&x6{_Xy{$JL=6mwV6e!t6NheXrEuI-t>cdER7u=QDk1Ll#6x% z5X!qnYTNF%njjK=^bYM5mFnE+-kBX!a=Ea38&QXx|TR=qvW+BMMB!Kyb@n3K8$+C zC(nn)NeW{WPThTZVu6ggRg!yH9N;47kQvev(+kq5oG{C1+Ga1?^XDDaG^`r*X{uT@ z74iCoC_z!C7g5Z^XBR7jmQBZigB!R4vwA$DR76jkKt}#(p*D)IKF}7S!jYAW(Qj|_ ztQI$ZZ`nm(AZ;%|td{>HekgSdJ0>w!yDMGYlr629@s{$3k6SvQa%<-TTUyOyRR*B> zYj5lrUxYvN@J=7_gSA$=pEOwi%S+NR>Ihh!nG`ms86EC%;p`r*Z4cmJs~a{$&_>~* zcWGiucoOA*2?n{87e?hDzA5>`lsb9RX_(|ic^Dg)n9(2`S!7bkpiae~ish}D9i7r} z>1XW@ckPtrS-gknXDtZG+iWv_+C?51JodHscGJ+(94~eQ%AznDi6ntv@r{2#Kw)|w zil=f3KgKYhdzy4fo4Y{2vATNbOqz*44hd^+KuM!i96_uFRwPwO1E^$rdloM?d~)`a zJ$80(CTuDe3ttpmv6g49@VX`5TfX*wIS#4?A%FRPBiGOI471MIQISjS{Uv9^L{(w_ zPxo@J>eZOVnl|R&#m3;7+pwO4Yi2iV@v$HGi6m_EbZ=rm>JE#@d)Y6`4n9TSc)uF+ z2V1$gj68SGe(e^;9M;g3(ca=(doC&JP;3(BJbe330h4Gg@Ls~Mvc7qb*h+P^U`C9| zW80V)B1b68MzOEqeI+Q4d4T=#Md9UZWiR|+@FukVA6S7cNdf`1=tzhx%8hp3QS*qN zYS7dl=WpfO|4x8S5X-`va~Hny?h3N^EuT}q_RL{j_!BQVMh*43X_mpf{?nh8f2dvh zaU~a^^9|;G46@}FV6x%!`Ct#?{Uc>|HSyJ&*~RyG(>m8Q7TPui1ZTa;>l>eoyN>sH zmalEBo|fQ;i_WgDs+7HnrGrEW+21~~Tx<@;(b+&_c$;i+AlZck)Pb+A&`Ra=$lHH> zRf8%2Pz?f!-NM=T*MhCVs(&_2ZFgHDq`c2a?`SYh*bb&k2d*F4qjapdsu=uC32D03 zz1vp4u43)MO%dE4sN-LqQ!|}#$fRMsFvwT4!#o>1!u<^B{A(^`owb3cvd0(c8RcbH zHr2M-7A}!8DHH5#m|01*m|t}p105u_zrDXd=PF-D_HhLpmBwqTV~=8forXZfOAJWD zHW=8U`3_uQ<-n#P56u@VbF7MAiDAdrAShvcGMmRJz)>TaeZw&u8;Ksk??Wi*Ltjjp;soy%UW68w4ui@dzUp)tZno@O` z`1J(DKKPpV?_NJn?~uu4ee4rw!Oq^KkQKTzqvgUUw_iIvgLGWLthvrDgZGO`bbdq5 z;Zc z%mi!o^QKF_R^4+?TJrkO4xPp`E9`yx#FeVBxlKEM3(s2#>2r9r3}r3+>-OX3l3o08 z!7cZsRY^DcYPrXPNqNzck|79fOW&YmBZ5;rQ(_t1M^>GG>$+xD2b`1g!vjI`I^2$N z_8dNX&9pf_J{Thk?+*eqi|v6St|_U|{8D<=?JNgH=w@-k>hpeQh6?Vgx78`uu#!6K znTn2Z`lIRsLA{>#0oVQ&2n&f-jL~U(s1!;d+3Er~D58=c@Jjp%U&A#zc12z=@Zi+v zi08KytV_FApQ1YOMC%%{gd|M#|1>zINK0toKbl+6Vxp2gS6OmX^821)C~H zcFh=*5AuR0&mGr}F1#;jHs)FI*MQ?c2h0*o*A&ZI=Cz~wIV*&8QFucrK4;D-;j+qc zc}(Lj+*>=m3ON_}Jx?n$5H27W72a1)ke%g`OL?XC(Czqpcqymr#uDauy=qI1b)Q|H zOq9Pq;WCF(_noyI1g4G$0f`=KwKli>rU3>pHUphTk;Cdq zUK=?ll37}nD}s#EVub60-oA%CA=cI~U)G17V22WU@6Q<6SVjxDek?JuYU!ydeTEKU z)D*Il)$3Kl)jLhj-NC6$<5uE3k4kzU^@z+C*KluRCb4n3l@;lg+`V^22E;wi59qSP}YPI6ti@LYeA}%n|EYI23Qp+@tj8axJ4_Nx; z;vQBfjQ-9efu)U}n}WDzux`bYi9ZH=YeaE(X>vwr(HzuzTkAS8MGcwoj4STvI<6-|6C^N@WOPnhC>@7Qq_!@!_g4;Q;1sDYROO0h3!{;s{LV=Z{0na>>qiJG=*=H{x|cba zVy5t@U6Wh75^7@?ev)@AMAo{Tvpgnib>p=%!;}vy1G)0@pn&?m(Zr0i)C)v zMtYu=Wo3YVQYTXkw>p0U`fsZoxUx|u+NIU87NkNu4Zkm#*PYV!3;>LuX}AC;g<8-G z0A9V~tx-(&=4P+aVetP=R{Z2%FhQQjZdZ>ewFB8_^GS>VbEU|xGizhpsXm_)m?beb zA@mF<^e1lL3Bwl1cDLPv7+B~Qc|^?aVr){#Gaceekj921Cy=x(o292LmAn9F zJ92Ory!&}*J{PX^*^+hvo@Fa&a~*9t!W~I#sh)a}u;TeBo&Zx6*!MABReLGh-~Rimb1#=*e~Pr@j1 z{O7TUhAdwf0x{v#s`x;A7QIW7kFH71+1TWVhL&Gztkh0fuYKJjZLhDZY{h$+xt--i zLT}{9q`1V@VsXR>5$K)z?*EkLJWoyM~<=ZpEXZ_5UqakA0w;$tRlKXk^(?<|` z=i$#E?1u%X{-CVVM}=O&@uIu9Kc4h$9qkk6uB%0!x;yNbVt*(yNsheiSrI%=&ZnzUYJKtdf+DePYv}(T4p4BwYX}h_}V_EymCz7vtTK{LE4N9Hpu~<9X!l;{!#fkSDF0h!OMr_i^=2=m8gAL z?YE(vAdKK?dIzE+;jBOi0Q1JQxCmOsV>P9%T=zZt1DZ=UW*!2R`H$g(ImE>>^rN0<6*iEFz^*# zLd_XlT9^nK7OkaU%Zrs}gi-%IlnmDMJF6;bAVuJ9w~{JpH}xNMK)Bpu;x#ZYXu=i- zdqMBpu~OrL5x$*h%Igo~B5VgOyEQGAJBwKDf%yl>Cec0wL=B}a8LfvTAmC8rA=v_) zxvX8@elhJzou=u@B@imuUjwfi2fjNwQ>G!{oP>#s=^am>KJ7CE1?gSBWtyZwJpG|f74g$(K1H^885baXVmLr`_~sEj!C5lbC$r2~%%6QpYFs8=Fc z;5So(CoZ1=DJ>fc$7^6{>6MK>aY$MLU_LFIC5%6`=H0a`DBv(_e%{?Nl<0Vd1imgGBNaLxU39V8*{KtY!(ih2Lx&`qP3!id_jLu0PB7AZ)YF zf{uaBb5ytvS^dAI&%c98t)Rn3DnC2p`)a^?4N!wGSB zmH7?H0(+Uy;)xTdsYs+VL54T}Y5`y__p#ng&hKEuV^LOI~<)x{(rFtD6$2>QWah>|!!r}s^p zxIQR%zr3RXgupnaLH2dn`8F%gr*VTCo(3d7K7+yJxz}OUstb`?ROXHe2*!j%g>CGB zWnmnv-(LPaB^I5tlza(X*44%NQdzl|Yp$PBZuia7LYh1|NM&fkkl?U$fSpF4V`A;$t5i~mr+?g;$bjGA^FEZ(8Dn<>j~QxNUW@zc&v*NJlyAFMAlz(D#uP! z|3_zcu=HmZ0b(OBCnjQ?z+ji4^^;9rUw3{S07(W*Oz)&`-@d(D1DQ9%5A4Kg{Y!`H z*0?F$^Q9bKApbIyj6pzydS@M3VqiXi{_^$Y)C zt}Pki5v=2O{NGXCCu;t0i`@Sgh3#gLy&`#X14Olx`)V;tq+fQoL=_m0YrlOexvM(I zk2UCH#d~2c_=aug84zq7AbrLhd!OF?_=Js=7q5>n=$5NvS}}UcTU8%t^QfWRAWN5R zreTcRgU^4fvoW!2R^5ra@AeR?`4wL@2JtX70a`>GFM}b&ATr^#EK9E6IbJz5n~=ZcUZ$ZR z!6cOpqh8#Y8rZscBF3X5tg7y9fLG9*R|pEXZtv-dzlvgbk`;dmC)_W z?XlIf*~@Hulpn|ObNom5F{5CfnK_eFRe9yr5oj>VDmJ=CR7mY=-&)Pfuo59E4sC&! zI}6?x^UQ97>ksP<@d;|htq;{wx59T9%L0!)YH*=DH*_ZO@_s=Eec`uiv)|C;vu8F~dXk#x>#e7! zJn#Wc;ZS-&Q+kyAZ%e9U)Ns`?`9Du}qDiXi>fJCxtHN+^RP~3UX0Z6s#MuwJfsQsT z>Hr_Q`4w0k(!~{Ip zb4P2e@w{?~(t$jZ)>LiOB&s~1+!MWl-^Z*+Tg^;?9@T1#u8#Bsm{~zerdQ2h%@2aNPS5#D@#>#_)Zpy4qF;i}C z*g{GcV3lIsP(af{1enJSZs+piPdCYqR20?h_G}Bh<>fNFPfV$|mFQ=m>yrL)%F`bm zZX3%Y4YKL1eJI7m;9feKchrXIm{+2#BzPq&7l)Rw=t~R4rgi2WK$cW8FW}yK+65s= zOfMo&T$PR6$TSTbE?ZCX9}M8CE4`A#wBB=P*p?gIRs3>75iZN1g_Y4*{47O!RrO?C zo zW3N{$FZMa%n+X0lM;X_1yjqLBb%iiEy<*!gj}L3MxGMjU_oDJQB-eoOU~%TCJ{Nkg z<=PH9jJ!u&Z zfuwVF^ho~Yu z;pnT_Za-W4&ZX!L6{s&$2<5?byQ)u%%GPJnSgehCsPc71+V}&GIX3gP9dv-df2pR^ zOXU4_a+|g(L5f=MAa@^D**t|zJzukpUto+45o`w=kT+IbOv$^`|1^OC3DvHyGQ;nH zY0%+cMlGL!N`Jou=Y8;HMhS1TO$zxz2}n(u=`f`2SJ<+bo9)iHO!r8LW$;1eU$dB5 zKpb2wZH`(~44I4~&T*QO9#qm(JtlVZMR6RycVH+9 z=a=5xG;EL(!+4|vk3dlxBxYH|PnSfcy3>EUH0e!{F0N@8McTElW{G=jCYKm?<)bqg zkJ@V`gJc0ex;&Xg*$kfbFscm3yIB?UR7`IUcS8Pq0;${2> z3K1G`eVVE6NO=v6^A8b=|1n{?-;yXK?x=3wD6jSZ&4Yl+AePz1LmC9al7529GeBS_ z#?!k-q{pffhQ?l2dsa-z%ZB;Y)-@_pwRJP;;5oIdJ0~}v2VuN?u?iZ&#f`S)&(OsT z62)yV^ZoB^VrLS)5ce`uY-(#rlr+1S^y*%g07a16&auxNtuH76S$d&+W2>t-$ayL} z5wcmx1ML?FhHu8%zPRtgyAAfnfssxTdX_z5n383)-6AUO%5)q48aSLSv4JqQn64gi zqlvTj|9tmimgRFLkGFnDtZSs&X!gXRcm9KY3OIt?iYj$`qEH%ySCJ#tqRQvZv3-~S z@S##*6N*UW_F%=1vWxPgKbb8%N%uupG}Yec6}(WF#@yXJQPJz68_`Y>pnMS0gEu;U^sDK=zec}j_wZ$UfcXp3xW@eYY93BG_U%{cq zxtYd%L;ZU>^b?ET;rJb#C&alp zySI+&73Q&d_JVYxp;Hl`=EHXh_e*VBhAQ=O=hTLj2(z3~ijhCw4S{&uQTbA!O?wXK zN3iFW&{^dzp3Q^z1kTSJ%CIXy{0Q;AZ?!=RFLhGmr{g@H!Zu4$%N2QOsL?MEqq(zs zC|P{rhi&is@u&kiVp&#edkxfzdAlr^hg4M6gf@5R>M{_9GklJAevUlk4DED(mz8WC z!jbr+w!v6-D!)~h_M!RNNGdo=yUDUE6#)?^ny}7#WSnzk(j-Or{+?`Z5p?W*X1BVs zRvn)ywXP-9;J)q%;=rd^Agif($R4$t+|}cDwzc_$g0NuuAuV{5bwh*<)o%`M@WWtN z_Y$+JLn3Meg3G3?xGi_G&x9)5P^SaWG0!Hbh)^WSR=6-uN?xzVv zZ&siS>yJQ7h>>E*AFZEtefMtGcK#cPBrbU1C#%iF#$)ZNp9F^8{DfJEi@JH?cTL-DcbXW2jT3Gi?uoqt7AyLc_L4}!hD|PF`g=2 z(R-u!@Mx%_AXvJKxtCWOKla|(*C^9(z&4tn5XVR2vFcqM_@S=J(5Z@SSfu*HHo;dO z9nai$;XM#t>__u^Qy~UPF?LLzj#&Tf@&Kw_^rPtuTj#5mRy$F{AuP3wF-6*J1W~Ja z-?OHc{y^g>P|1Y%+#6Q4p}69N1e_5HG4aO~xKkrVuQO>ggpgUCeC-2xoNiBb{a)OM zNiU`2R_vFyW#)vi=^+9V_6SPh+hbR3OY})TmC7)=Y~AY4upcPhxq;b(yf82gS!+z^ z{(Q~8OAl(#Q2jm2{5KhCz*G4Lks-XJIaaabgawjS%Gwu)1aT>JjsD0n=?gF+j3=-| zExOxDbAaQzbgA{I)~Hd*+kBfzmWf>-+u3Y?huQ^70)#q+GmK7+khoq^2Ol}TQ%D<)#eth%N^-N{}pScvR;oTN8RUz_LMtF0y{dk_4LZ>XVej7$Cq zn(DQq#ug`LC<`l8W!YQpEy*K6_V71f7t)@ymSS}w+(YxKT$^2?FG)&W51n!uC-{84F;CJikxJ2czNsw?=#+mb$Q7R zqiTPz@1{?bI8x5v{MK*wH1ipheT&RFND0!+bV7AV%mAh14blir3c*(9i}<+)HxkT0 zQs;B7;REAM3Zq4whL$TU&m@R5@-ei8C7HN$vzm)lYd#iXH{WUTXbs^LoJ{TJtCR{0 zuPbXoy5Y4fsN>8z0?P$%L4n{g=fj6$zp!4*KPLNb=M(d}tK1PwhChx=?qmnd`gsBg z()ilF{=d0tvw_54LErggID#Zta!LE<0l;*`qK1O!yny?2!85zSw=Yo$3U#XQDpWcb qJpP>JuPbKR8x&5S(#gIl}(nWmQI>0m6<6bB`P5z&5}-2=46QD zK2D)iF64rQ8*QT{A?5-q0+pgE3WWj*0`CoI)AaPe-sk!KulIeg%kWC>oBR7c-_JSc zb3W&A>9CjE;?Ipg2LNF4!2|nz0AOA*_^;s8x!_+4pX4Ngzvdu)-1Y($ZL3EB;49$Z zzCGWa&l~4L%ffr}%_igXjQ8L_IbC+(;M*-h2i88we4Ii03xeJyGG6QIG`}OvbdOcr zub+J%U9=TD6tm>0Zr?`<1+X~o%qnheRquhs@_yK(8^ z*AE}9HT=HP(6BU~T!IpJ!^C7wK7T67w5~$NkC%l;$PqZ^F6GVn;Oc;TBfAE1S6jl| zM}*!@w0H~v9AC%`N&1EuXSdqN-K?i>I{@4%3z{m-6E|U1L#**Rz=iCC=sCcrA{J{g z09=^UVt5m8s;+l|Qs1g*({yuU+}k(x#U9#Ayjl%_dtQPh`I~_jb0VEie~`guv(1SP zDA7&4h^FpFF)}4{fiL=%HyaF{<=OH)b%ziK&z60MemzxK8tobydq7$G3PEc+s&q^d zPyCX&J`4Z|gbD8SPu#q>34&9$n$|~Om|FtC{iA7uHLdIE2Tv4)UjH#6You))5pgEQ z6MU#4`NRPgOTihVM4fp;?elY9Z;j#GTwe%0o{DLUQ{u98+o{+rSmSP_oDv>h*OS3z zMOT08ksRCx6+)#Ay$J!HEC(C^g}||vo+R7r+9d~f9^;An1^bgk_ThQ9xHUdrr?T#} z3FiW<=*L0<;Jl&Lv?7pkj5o2`WEZ>Cv|iLfbpY-u6xlJhLTNB~MO)viO*W?ln+)-V z2e#GZOERo6g{K2eq6$jtk8%`30^3-*IvluQ9Rh`}jO}-Hvb5RWYOu=@QG5c_kj* z_rFwV*srD5&*#4_Tn9iFMT4GTw?TVo=311r9Si-c%0+t18^N7#hsF)Clf#7@cW=?1 zs_p`>C)$lSOXe-oC=W2#@Fg*etC= zYFF}u?*|u^3@-TRqmk(EeP0GNC?{{YOgV__pPIeRH47#}q90l%_S%1B1>3%5yyX%E zX{rUMzXq-I!8QQdpAIti-O%5!;oq%G|Hv@(_ei+|U#T99cX|855Ym?-lF8fKeEpGm zFoIcC_c@mBbF2=B`pvp)S8qjFY3BwBGfSwREO%s z8K_5e%PQy(A2Y_*sAgx@6=)u8QTS!T@Z+sc6{rJ@^Zdp%K77Q`WA!jq@wx}T7x>Ul zR|E^%xILBWK~AKa(==|l%w|8b^juvSc`~*9D%zuPb(AKDxhiG(0iA>EPUhWWQ5e(e zyN^~ei1CFq2N}9CC{51gpv8Th-=gh~eC$ErGJ04=BN$U5^%L%!yVjksTI6Qt8}7UT z?2C_EBdjn0cE$4EL7fkNaQdQ@=7Fng1~6>{iS^9(HA)L!(fHwnHr3*dzkUD%5G(gE zFeC6%@Ro#@x5m{5WruXQ)@?eCEvES_Vn0kPY3xnQMK|4Z*8-hl?w|-&@uB9w6Spa*?)ZgJu1woq<(T$ zOHM@9Rc>2ORPz|C47}W?%dAeeO|&{e?`t32^QX}#_6ZI!V~*O%(Sz);5H)r;bO1#U zEMPb$lWsTM8EZ`&O zx9{|#bvj3Q`M(Po?>$s^X>hmU5?XwG{Dj;}F3LXni+S;H`j^&R{31u!ll@7jPj+(GknVjLFB{Fe-Gk1$lVPJvb})3hMThh_&r3;c%G=+ z>~%@i-8`{#^0XZ>4vnV9BY_Lc60q2OpE~Thi0@eXhnQO$j1Je+-cH~Wg!|_Jo5n2F z5-9D~*3s%v-N7A12L#*lRaQ8)L6GeyfZYk|ACChNPBZV#Y3s^&K=G_Mq^AWXrb{sa z6&JaLEAZuKf6>`Wre~%jaCvho4r1dze?l`PSY%27U3ERD+uLz)JuGmXFl+*t9tmnV==zK8I9K_ zs36KOU&TQC8%IL4marjCMyRJ(*2etnp7OO2b|-?S@YndbkLXPkh+M$Qgrx_k4u(IYrhH ztGach(>6u6YGjlf8x7+*xOyysmk=3{mZY)zCK@?MZZMXrL+$=_!5W_?So9M@ifFx( zo<(8ppg5Cn(^gtiiinIb=3gBIyB%S2AD%e4CE;<6njh;`BG?VfDP?#BBLfu?Q@d;? zFEMJ&rGXjB1O8$n-#{2#NiSRgk=LE7%iE>H8-pOJHF4MrD~qH)J~naM%A?0d$RIfE z5Z=u7$ZKgN-Wfhl-CiYyb*C4s4urY*XI5ZC8#$z8>H3&qa%%OW5`A8Ba7SlZK@i8* z#dDZUGHWW+EfrOPJo=Gq7U7?xBKEf6`m|s7mc?Ek?=A{cEYDp;o^cp9O%u zTDEjHLB|^3%j4$%b1=V$`xrPEXnRJ&KDYI8*Kp0=y7;TAlNnTb3$>_+T4d5A93N@g;^Try4(2 zK1YQK{3Do`Y>z5CIHaI2Y8+J%7Tv-%O5M&gkX>Al-kKZRW!6i^De9!|XygF~fsI&x z@7U}Ny=$t*W9L&2y8oex;35DSxaO-`_(#~7aVc6!r%7Ph~Jzm8)nR(`^5W| zYNh76nYTH1%uEA5Or9(xfo2oBxg0Xbz9>oFNSu^-V>c5g1vI&z`-n@R1)ovtncF&G z3~Ro^5)&Q6nO>!2*Vc$?XFi;NI*rgo2`&YiXD!5-HlxXLC3t5kMSU6Hp?TY)-s?Vs zQ*3h1^-(A^6Sg2sc;3l7h?7}3xrsSZNkfw3!`zZC<0={tnUrJNSoUs^R_4xcw&0fB z*=KkQJ@?1eEA5ug+Nlz%!D$lQM??tKusIQ`4AZbYo0JN%nh8S0HJ~e)A6n=!6!9$uMS7YQdp~M^nbR0|cw*kxVr3+htwol4K2azB%7Or8w(S=AEXg z@6B%Fot4Y@iA<`Q;*w!8UOnleV1mE;_?iTPjpl(^bqU`=Q{BKNv%t+utC@ci95Tj( z5(q+jzkAMtRvYMz*crY9;(c>TLuXkwh#w_L9f0R!=TFWnRXiLq%f{y7zE<7urF;hL z(`PoVb1ZXF-@A zLYgoKxTcFpF_Xu5#wy42->Ay79coVX9v%(ud?3PClCG}hCdswzqk=@@lb?bF%EujX z^Sj51a@;gsg}&4XKvF7XQof5(AdDx!g>tIVF@gSd=Hg`0p5a7iSB-P>H_MM7XhWH~ zBl5jbE$s({lDiXCiZ`!ZVclOJZ2tLSPN>FPEd-q*Ndd|L zW{s*;L@`Y@Deix&n#R3GG>8*YDy*z8A*M8T74x>O)L9aeAt7`RM`4EA>bZ?xS26MN z)=}J|(C=K_>v0QPDy(g2M)fMi8^P|&OzaCNj*Tx=XZgC1&~YQQ$o^Q(=Z7^6LXr8s zojN$0T$3yjM0JXsDU1pQiCD!A5CguJzlO3qa;aj=V zB@B3F+W{e=3hn{hp>vQph1G3%SB53;|oTMl9Cbv!K= z%F}ipM_+t*~HGzkhXM4 z+2y@n=&fh#MoDYmUzks*jqgMd%sSg0ZFFnVzM~pUn*MD)%)#0rURR(sH1yZ4y|+-Z z{CBKD#oDt4fff4t6)fp8Y(ueYiE+bi`C!nCwty}!E*BWjJyfqhXiVgDTxnWVMUDs3(=uF|Qjd6f-ir=tIu*A9M)KG1wvU49&l@39*lXF|b*3(sx^ zizsg0XZNl;VkKv~Bo%9SvwL7a&lGDGTS2fuHBs&(_lKRYg9+}s7{UFpym15B zo-Z|8x^3KeHZyFic05^MS}4+twYVfr$hE@g(Y^(FHtfZLvjw8X$us>4Z5JkbL>8ND zcbFVMpxOMQ?*1&pn0xuv)mPG+OMu#0DzL3Oxj1(jG{6!F`iQEwty)4N320`?Y9{y4 zn^T^@TR=BVD~usCv~0m?zll=|KU=(wAY3^6x9|-JC@ov3zy{g=6mnkl(lyBTdqyx6 z>K!Vi(&-BV=jUZa&Y&MG!k-v6O))@um!?vcn>^UspehZbTo|8z&c-1h1kxr8)!q1x zZWk1J_vH(|JN`DsWsK@FiHLXByrc)&&hNRF6#gf)!+QR!+q9HZI(PW@Qts1N&up8> z@yjl(p(>wvUkT)amv4r;{!^RK#YG$2JjsMfq=z0aZcA60E6CU@dN3v$Q!ALi&GEV)TZhe54ywe z>U>m_)%x0`iV3tO|Dtg1@Ik|(Lxx3VO{sA@$MkBYzwAdB<|b7L-MU8B4)52!9UX1> zWae}(%Flw%gz|h6meNP*ZCaC3lSfwf_R5k*z7M+e>~f!+!~LPa%8#}(9)kKhU#?fd z4x(SkZ&69(y!-NH`4#Gfl-S>k%D-*ognHSBne6Hl2!u?3EGOmF_n5PzjeqWEpv9Ir zXYB9aVmtVf(1M{nX8nv)Y}@OZ+_?kg5;n)%ADeQ-*wZCC^)Io%h;tZ!xo+7AA7x*M!3snGdhCMk*sB7!$-# zME7sW|4^5{62YQP+9rCRdX(B5bY88UaDLZtcys=cxf52RYYNrIEZ+mF{g}>rkkn9O zmXy{tG&E2P24NnBrm=04U$nMSQ}?y{W%-KMQvC-_GqUtv1gfck!#aM*4>Q&%`#PO^ zX(#KKwNt^~K^}(opCo=74*ql|zKbCuVpb@aVa~rkF3C44`KC~=*zgV)etg&2(+OzT zC9*_yW-91jiyf2RzAiRg^vty_+jKAZ6X0n$q9@@{XxKSW>>$o$z@Lr^9K;bKE`|?& z?8HqS-26mqld+3|3)Sld;Txv5=1m;A&WL-5q_TcjEs*lo{107XbR~m!{8=Wh9FC=K z5H!kzINcqRoW}H!FM$h!+2oxYZ$(f3rI+CcBRusbJWi)$#Kuv`v>XE4j7XrW2B^%q zsq)g&2mRpEgDN4wJ*u8EzQUsp*te7)DKuje8yNv;gRo9fBr>Rvb~R5-K=$W^%dCnU z5ETQoesnZn(f|*NfMGDur0vXyDaBHO(HmSp~(l={&}EmjG@wwTrRq zk~k-@kJf+v8!r#f70wA|Q|+>#uJos(tyZ;^^fgOE`9tazQO-wKIV58%2y(e|++;CU z_l!2^k&ohjO9(B-qW(B-8Ah_VhLpLaW9cETeI&w!-%>#+ zn@{iYK2STZu`Z9AI`y_+Zo*hF zkla%C;sBb)8|ID99sNYd;~26>Dakn(+LgYik^SsoQUVB-f@6Td}=tT&}W)7%5EUJ#U2E@@_FUCugAg^bb}2 zax(=7v6SS}MRP3~ZxnofXWZ5{P}cP5=((~DS9uB*tv{pE?bZ%z3Q-n!R&L?<2m-Awp+8u^bh z&i_jl^C<0%I{zi8?L&GCvIRD%?i!lL9aenUW<0nHVUK|D(Ao0k-H7p;tAnP2^{F;B zXSwstD~xdfV2abu0-V$-!~H+wDW!!yQt9;zqQHr^HBl$fPjZ}=Y*?)t3GtxSb#}yV zZEdNg2^3*L&NRW``M#+fRz^{6x7qc`fRHH*e3m7RC!@OdF`w7SCGBs@><0u^99w_f z7TPYh(^q4a6yh4Ry%Q4y3}j57X9D+a1vZff#X2T4KtJ z8JJ|6VPi#6{BYD%dsARy+n1{HWLO1e_jfuf0dq&U+%~#pBd)VXKdC)sI!;zM8SPYeSEY_a2=m@WgG7oq3IoGoo|)nC9I^dx;!U~YxB#lZ|$P(%5G^Gr7duQ z(>Z+;6s~+KnG_k6=8L8*A$P_b+Ij096=W`0lPknq6)D8IhHzQ~BU#p7uzyiwol`_S z(rA}b6~@VT7W1kr0|wez&9${nv`lvSlhI;0JXA>I3!(lVGAREx5fdMP_2zKmyIj|ViYH(USNWmM=ZB;ot=K~S=|I3royU8Bv*?OL()e~R6h zw2-x$z;o=*P3gua*C3wX>u$f)WF^vLB&n6GMOM3lCc`NIcm`H#&=oQ6)tA z^dxgqYTHA0X#83AHF@G>0ePjbPL3$god@LI(Ul34y;(H7%|iV+1qi3yO9>^XtOFt$ z5?Azi^8Q6uo^2>GpH}EohZ6Hxj5&bU)9&u0?juwFro5XCQhjVvTv`m7g5ho=lo_ss z$Co1I&+q@3OEXM$bsstJ*aUND<%e2PlAlmzbAhrEi3C)uUcY$wnNZgTh>2@;@SM8z zITpF0imSSc{d_)sWaBNax$2Rvw>dK(<{-s@57nzn`(3gqrMa21>s_MBBC7U|X0a&1 z_UHoOSdNY#@GkYnjuNb%buxIxPJ8x#*-$ergbA-ySrb+QCxvQ0`W2fE7HT82g<8il z6V`ScQl=ObhvwwmLUJBLC6q3quDSoQhcl9Jkl4Ij%k@FF;QYi(P}90W$ePh&{{qKt)IzAIxzpSJ2p@MSP6Cz@=xP&P1DKI$ft@|?=wf!Vy1kQW6LG<3gCj>s+tG0g}U96nu zitykO=phKoC&!PfZr0{;-4nh~m_4iv3W*QOu#=MBa+9jN?{DQZ*evyTPkzln6ytg7 zaj@3>^{+;4*#yp_DDIOqLqCyq_}(N4KS#p7A>%uSp-E`%A?_I*{i8sKxFu7C18;uw zNmL9TaUVHr7tP%~H*qoz0q4#KzWTYoaC+Z~n2Klw*f_iutz+n5FK~-FKWz>$6Hfl4 zO!c7{)L*Xn7W_;s8xLj>Pu@9RU;BzWB;BJeoYz>^In?=2D7ir_b%}SWIfCW6RT*&F z_OpU51FQSg?V-)JMbn#8`nJ{Z9kGn0L``gyGLsz&l6FliSUfKQ&OYFsmtMqoL_pOq z|19&@oze|oUX^0eUBsCfoP#UIsBfSqFaC^!3VRR<8w4SwQkoMnauJ-n8hHLFc!oE> z@N5mzpD}p1+q+#x z*z${BT0}k842s)^`HSeZeTya(k6Q{rVFcLy1z**cdy)EY63opc+l9cL)w)fa|Brf3 zy5jn!>@RM4E~rwynS65N2z_SWj{Y*X38Rscit(mNA}ezu$z@8-N;`=vsDBGiVF1tP zgR(;j9@K7(GNt0m7eV{{KPD{C1`9}qKEURX2Ln~r-M;i7&~ z9yDj)4c!D)7|=PhKSmj1z%kMk9ggL5C)9>>^3piW48aD*??7n z^Jq7+Ky~wrgi-2cL#_XL-IGeCf4lGom$yS;y9A+;$-x-yAeE6~xN`xo`-aIX+f8M& zGR{Bc5a82w{wwtn20X3R=`CP0VN<$~yNCAd^Bt$GGp8gyQaVj}YN5hnkD~J0diW|L^ zEw^D~{qXc8hAETklE;EUE6BptspV?R{wlt&%g}6x`(}fy+*NR;+>z*1l)YuoNbSvO zW2Nt?Rfz(-nBnv-qy-mkc*~{0+Nrv~G*xyiIjz$<>kPD1t>RdZpKr??%w*q2e>;w0 zHaE4jY(~%;l5pijl07m+KuEUiias&oUD%3=w%^fN5v?*8PDAhRrw0@y>>R3VI=5L* zSrRP}_3FkWbk#{lh6>YLDK87-I6_prn3$Hz7MfE&+{}L$JQum4y)!TfnbZo2v^VZd zjGxNqROKtkRgu`LP>(`OOBY#=Rk=$#97o5T$bmCYqv?Y6zSSz32C-Fzdm$pgWOlM@ za^!fSP|IkR4t}md1*H^=XPf9S2)Bj6JMBSlIo$~EUj_4^ zhZ4@b2+mdnJ0&ox17apj)zIop!WN#X+EApS&vfAH51=_MqvW6nnzjA)GSt@2{byMS zbR;BT=+~Ue2F!Vv7+7K9+{L$yq{j*i)3^wnfye4b!nGF8#veNO3o8(YVg(`jSf@sg z9t^myOKJPT!-?FZQOCbBO&kjm*OW`s;1lir)MmnBZo24&Gv+fvffbt~p@ZfxhPnos zaQZ5-#aK%fIfaLUh+EK5jHj_blAJB%s5-0e37mt@^oFVGe^+4?$Ph|upaV-0Q9Y#D zD4+`b18Mn^I+z-2mz!{^bx1K57icorQlf9I%`k0W`_)TI$JLJFU;ikkDhJQ79hmX~ z``_dtcT$tVakNLz4}5vFA{uGP%q-U}-^L3Jj>ki_9H8y$e-uzYBp4{z*ymI1U+Te5 z_Rc7-LNMId*8ne&AI(coPa5mh2T!iJ!9EJD3&z?Bh7N{kGUreYEy-V1C-9SYN0-nB zdm7ubCb*;q*-0&3ZmQ9ZSIKnAz*#9-=@#Co<0&Ri1WJg@7PyhQgTEv&=HX$H1H$Aa zeM+QK%6;ACA}*)S9=Laj`mS$+r|7AN%>gvU$10Fg5Mg&{0%Y9yju&UoGpQ{DZ$f+} z8Lpjlp_f%ni)`G2JroHs>Qg57^;R8XoAyC*Pho+aSTDA{u`B#B#!R_2mlZagXvqwx z!aQ&>{;Frcu^vd}J%2Qr?aDxo!+5nJp`l=6a89y25u)4i_&PyIMDSQUf$ohsZr>@n zQv_MWtYNqQQnG`Xa|2;2R2mi2g^t!w!~BOx@5(OX7oJ7*eL9BNN0A9)hS z9I3f+1cy)Q>+i4~g>sKstI*L7EuqVb$tPZs7eTDVO4yibJw1!#OB<{yt4u!V7P~c! z19idb2c|%)!wPOX)EvUaOrbx81ew6{8{7S_%gFsdvWA1gI`b8NhWCt2yJ$W2a8pG>jYeJ#fqr;R;{5(SqK z8?e&ed$GzW?;9<=H*7I+NUVV1IorD0)Z5DfiZ`$kQ{NRK`p&JYS5X74W&I3Jo6O2* z!dMwpQ0u4cHB*ibVR4yhs{YfwupPA*JB#fykSepJzU^Y>8ikbXXv@w<>yZbaezb6E zD}-SBea%p4l+1c(hwwZ6#MlN|rYC(RyCx1(EXWbco2@a@nu0s~H1>bVB|D{4FQ-;t zkMvRVyx3HS79u6^N{ehO>S zoa}m>C*NDB3k5eD%0?B6UE1MB`xD&w{c+=+vG5?xQSp==X`l-usf{?S8TlnH{LC|6 zpxcgI3fZGU7X0m@^;)`YXKuL#JhZzt0|1stUPO1C(G?~l;j$PHQ>FROXCj2wnRi@0tq8I6oxDOj7uWMVZAH7R6HylN1(+u zXZYdtW(QeO-?TFGLAFI}cG1id6>rHz?aat>!uWaCFnzpVS9uJXn z%Fx6xjE6^JNDL0mjjapj4SX3vb!?nAE=kKI1;oDU_jE8GFLa2{LhX!_-3_Lt>H8)= zp{sVnEyOh#xddM%>pDcDD$V-#GI~1Ilai&Y9Q>*@POlVQ$gRk6EOmRmz=>5tQza~c zSJ%gA#Fecr34!Ui1DcxPQY9P>3FB;o8%P`fEZ$UxWPt;b2?iKvV-<6GYM_s%z$rop zppW$Tv^7F54{S6yYID|CpsInm%?iGsAn04qUW+#RummaSx%X1?4bO3CA@Do5NBrq* zY5lUIq9R5OR%&`74^#}=g;4Fd*wjiW5A>+ryQv+2>L=M~P0cS&u>;_gGAK;E5^ZvM zQ_!hW7z02=%dyZEwsD_;rH{Ve`!7nLv~SR0mCSHCnzUB|7kQb}{vc4tz22Oe2aM^N zP7JxMKW%NHGkUV1Q;CGJ7`}D5EOt&Y)*uAp7Iw*rb;z7l|L6@)IC^`V98BRml_0gy z)3`@?%0{=Rd9tzb?#i7y<65T)LcHw#&-C`yJfdtg z4AsmrfM6_092Ye*+|bpnDp%YD4g_DP-~tD# z(Vk*VIm|#0!x3);jZj;YP;mv>hjQ%WbFCxb`OIzh`Z2KF-y}=8ArLrIegalozOj2% z?f~n|6N033Rb6BZi-}5YRwn$ls<2V>tOw%uyGn6KGtk+gCN$aBlEapfbH=a0CwgSe zc+(csQo#;s6B?<=8N|H0(p6L4(4z3)DYm~I+1*pVG*cIEa+)Px9&rkzQw%3o4S6&Z z9Fjl|M{(M}Ay(SQ*w2eG>I|^lbl0B2coJg+pN{be^s}Ng2&iMYowAx0ZJBRHnzuDI z?p6!g0q_NF_n zP8B%M+4!aITo~ZIPGc{pcVy_U<^@w#D$WT~!)qgD_RjUE#VV%?7vuU3VKC-!+tgbN zu6!u!C|cA|Xb^7gR^OjNV15#q_*l_mPuEpg2bY z$^p3}Q)202@@)sypKxmkU)DVbXy2<7eL$O59*nR>kCUu-QtGn^)A7{c+QM(--83F? zSi}hO_rFMG_SaKR1_2k4I+wotEDQ~sT*gx4CH=&fHrChVaFD$fo}xVER?oB+TL)_C zqv<`#SI29ZyIAN?#$UXx$rU=0yX!n^PJk1MaU@Jg2}h!u$X^|$@915_+iux(OF)Gc z8;oBJj6Yq$J8C2IEhEaC9-*`81CW;x(fM~PA3AHx9pYHM!RqBqY0esYD6^yzR0H>d6g|jSyL{!ULln``+IY0HF%dx z+#eO*YYBIEP|8fCb>~Lk)wrrpYb$#XG6KUWHsx>uq$?UeW=cnoH?)kdewz83YzeJq z^<2xDC|)4MiI#@NVN{_fhj$^Rg>dF~bmcDIRLC&d0u>r%fnC0tw4B7u`&{P+khhN( zj^;n)hN?Zu)5<78cvM&Vzer}?PBr<{C>22{-!Tb)CGp)D1=e~wVY|Gsamv_;qWyH# z=Fwp<*ioB=+%SGXCok7EBBD>3NG-4_cT)B%V~2^2@~x=l#y%R{9DkfV?!QFg~Zm9sUOwcOR=Fyo6^8Z5U)#-HFA(-6+oVZ#$Bxfyq zH@bA0-urZR^hr05CzZy#kI4SYZ_R54=WvWMi)Y7H!uP16H}bRR41*(JlFXvK&bui9 zR?7Z$tL_ZJY@8Rp@Kvk-kBTEy{TSAPjWfh|+pD&G2eG|V`tN=2G2OLrpm`MP9enVZ zK5O1=xTjp@z9(fz23Ye3m((9ZX_a8)_fDl~3V}2)L;yR0HmF!Nte*<>34M-T6#lhp zI{q7aR3u}Xee*mY?Fpx!nB^h%BLx1P`Dp_dH3a=J6}z01u(kq zwYDmF5*Bl%d7h;Ecpo@)cdzE}>9v1Mo_yimw4=*%1RSyKhJJkLWPVRRsB!axH>7|E z;~G`5_Fa?dcMxbE=8e}f@)n-Y%3}zf|BXzUZZg3X8osua=MBQO2` zp49rcFWrconk7SZs{r7np2mp5=|upzwhDZ`7jQnpLhK)kIVU85-|t?hiy?n!3hkG^ dfjdDOmpSC=>`B818?^sF_^sEzioM5w_+Nj7PNo0= diff --git a/aseprite/Images/image-3.png b/aseprite/Images/image-3.png new file mode 100644 index 0000000000000000000000000000000000000000..c6265ea2b4d27fcd8551ffdb36e07cb8ed950be9 GIT binary patch literal 86635 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 T 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%0DO0!pWQ$4N(8LZ_ zI^7T6%`Late0oIBxEvVarlPxdTB+S4ANcLD=mAkr=G}39l4rt45F`bv(*v=uDQXK? zio?_Ut=?6pP^sV(zI15_*t}Ne`BCd8jGw9FctT#i;({knZlQr#g(jNI`OidlJEPAI z0`{PDs8FX_DewP)Q{-h*P<)QcDr2XN32z?@H_YoY2U{|5_xes2!=XPDH?Z7VUqMAM zLXC-%D_GK8q_sz=t+_D`b^z^2z$(LNm%SgF7HVHWUzGYvn-S6ugjDXqTZrWFQehwc zg?r899OCWcmnVWAX$gM7x+V$OLsMxMQ>HX|=T0?ZI-eSPuA3

Ny!h+dXW%OtA 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

+ Spine Logo +
+ # aseprite-to-spine ## 用于将 Aseprite 项目导入 Spine 的 Lua 脚本 -## v1.3 +## v1.3.1 ### 安装 @@ -69,7 +73,6 @@ ___ * 执行按钮: 使用当前配置 开始导出。 * Export 按钮:使用当前配置 开始导出。 - * 导出完成后,可点击 [Open File Folder] 按钮直接 打开导出目录。 * Cancel 按钮:关闭选项弹窗并 取消导出。 #### 「Spine 导入」 @@ -97,6 +100,25 @@ ___ * Import 按钮:使用当前配置 开始导入。 * Cancel 按钮:关闭对话框并 取消导入。 +#### 「Spine 设置」 + +在制作像素艺术时,通常需要 关闭平滑抗锯齿,以保持 像素颗粒的清晰度。 + +1. 点击左上角 Spine图标 打开菜单,选择 **[Settings]**。 +2. 在 Settings窗口 的左侧菜单中,选择 **[Viewport]**。 +3. 在右侧的 Viewport 设置中,找到 **[Smoothing]** 选项,将平滑值设置为0。 +![alt text](Images/image-3.png) + +#### 「Spine 导出」 + +在导出时,一般会将图片 打包为图集。为了保证 像素艺术的 像素颗粒的 清晰度,需要对 图集的导出设置 进行调整。 + +1. 点击左上角 Spine图标 打开菜单,选择 **[Export]**。 +2. 需要打包图集时,点击 **[Pack Settings]** 按钮,打开 图集设置。 +![alt text](Images/image-4.png) +3. 将**[Filter min]**与**[Filter mag]** 都设置为 **[Nearest]**,以保证 像素颗粒的清晰度。 +![alt text](Images/image-5.png) + ### 已知问题 * 打开 导出文件位置,目前依赖 `os` 库 API,可能导致短暂 UI 卡顿(几秒)。 @@ -105,6 +127,18 @@ ___ ### 版本历史 +#### v1.3.1 + +* 优化核心逻辑与资产管理 + * 使用纯 Lua 文件系统 API 替换 os.execute 命令行调用。 + * 将 Spine 徽标嵌入为 RLE 数据,并添加重新生成工具。 + * 优化了UI界面的布局与显示效果。 +* 修复关键缺陷并提升导出稳定性 + * 修复舍入问题:使用 math.floor 代替 math.modf(截断)。 + * 修复导出不包含像素单元的图层(空图层)时发生的崩溃。 + * 通过在保存前合并克隆后的精灵,修复 PNG 图层警告。 + * 将配置缓存的扩展名从 .json 重命名为 .txt(其并非 JSON 格式)。 + #### v1.3 * 新增 坐标模式 并 优化图层可见性选项 From fc47c33fecf095062800431ab57f4e4bece9e562 Mon Sep 17 00:00:00 2001 From: Ale Date: Thu, 16 Apr 2026 16:07:39 +0900 Subject: [PATCH 25/25] [Aseprite] Fix visibility issues during flattening - Delete all invisible layers before flattening to prevent them from being merged and exported. --- aseprite/Prepare-For-Spine.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/aseprite/Prepare-For-Spine.lua b/aseprite/Prepare-For-Spine.lua index ef49b51..c48706d 100644 --- a/aseprite/Prepare-For-Spine.lua +++ b/aseprite/Prepare-For-Spine.lua @@ -217,7 +217,15 @@ function captureLayers( cropped:resize(scaledWidth, scaledHeight) end + -- Delete all Invisible layers before flattening. + -- Otherwise, the invisible layers will also be merged and exported. + for i = #cropped.layers, 1, -1 do + if not cropped.layers[i].isVisible then + cropped:deleteLayer(cropped.layers[i]) + end + end cropped:flatten() + cropped:saveCopyAs(imagePath) cropped:close() end)