-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsplit-merge.lua
More file actions
280 lines (249 loc) · 8.28 KB
/
split-merge.lua
File metadata and controls
280 lines (249 loc) · 8.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
-- Split and merge frames from one or more sprites to another.
-- It preserves any associated tags, layers, and other metadata.
--
-- Test if an array contains a value
--
local function contains(array, value)
for _, v in ipairs(array) do
if v == value then
return true
end
end
return false
end
--
-- Get a default filename using source sprite name + either a given suffix or frame range
--
local function get_default_dest_filename(src_sprite, suffix, start_frame, end_frame)
local path, basename = src_sprite.filename:match('^(.+[/\\])(.-).([^.]*)$')
if not suffix then
suffix = start_frame .. '-' .. end_frame
end
return path .. basename .. '_' .. suffix .. '.aseprite'
end
--
-- Get inputs from GUI dialog
--
local function get_dialog_inputs(src_sprite)
local total_frames = #src_sprite.frames or 1
local default_dest_filename = get_default_dest_filename(src_sprite, 'split')
-- Build dialog
local dialog = Dialog { title = 'Split frame range' }
:label { text = 'Copy a range of frames to a separate sprite' }
:number { id = 'start_frame', label = 'Start:', text = '1' }
:number { id = 'end_frame', label = 'End:', text = tostring(total_frames) }
:file { id = 'dest_path',
label = 'Destination file',
save = true,
filename = default_dest_filename,
filetypes = { 'aseprite' } }
:check { id = 'overwrite',
text = 'Overwrite existing file (otherwise append)',
selected = false }
:button { id = 'confirm', text = 'Confirm' }
:button { id = 'cancel', text = 'Cancel' }
local data = dialog:show().data
if not data.confirm then
return nil
end
-- Validate inputs
data.start_frame = tonumber(data.start_frame)
data.end_frame = tonumber(data.end_frame)
if not data.start_frame or data.start_frame < 1 then
app.alert('Invalid start frame')
elseif not data.end_frame
or data.end_frame > total_frames
or data.end_frame < data.start_frame then
app.alert('Invalid end frame')
else
return data
end
end
--
-- Get source sprite, either from CLI or from active sprite
--
local function get_src_sprite()
if app.activeSprite then
return app.activeSprite
elseif app.params['src-sprite'] then
return Sprite { fromFile = app.params['src-sprite'] }
else
return nil
end
end
--
-- Get destination sprite, either from CLI or new sprite
--
local function get_dest_sprite(src_sprite, dest_path, overwrite, start_frame, end_frame)
dest_path = dest_path or app.params['dest-sprite']
if app.params['overwrite'] then
overwrite = app.params['overwrite']:lower() == 'true'
end
if dest_path and app.fs.isFile(dest_path) and not overwrite then
return Sprite { fromFile = dest_path }
else
local dest_sprite = Sprite(src_sprite.spec)
dest_sprite.filename = dest_path or get_default_dest_filename(src_sprite, nil, start_frame, end_frame)
dest_sprite:deleteLayer('Layer 1')
return dest_sprite
end
end
--
-- Workaroud for some unwanted behavior: The last tag of an existing sprite will
-- automatically extend to new frames. Tag frame range can't (yet?) be directly
-- modified, so it needs to be deleted and added again.
--
local function fix_first_frame(dest_sprite)
if #dest_sprite.tags == 0 then
return dest_sprite
end
-- Copy and remove last tag, and add new frame
local last_tag = dest_sprite.tags[#dest_sprite.tags]
local fromFrame = last_tag.fromFrame
local toFrame = last_tag.toFrame
local aniDir = last_tag.aniDir
local color = last_tag.color
local data = last_tag.data
local name = last_tag.name
dest_sprite:deleteTag(last_tag)
dest_sprite:newEmptyFrame()
-- Add last tag back with original frame range and data
local new_tag = dest_sprite:newTag(fromFrame.frameNumber, toFrame.frameNumber)
new_tag.aniDir = aniDir
new_tag.color = color
new_tag.data = data
new_tag.name = name
return dest_sprite
end
--
-- Copy layers and associated metadata to new sprite if they do not already exist;
-- assume unique layer names
--
local function copy_layers(src_sprite, dest_sprite)
local existing_layer_names = {}
for i, layer in ipairs(dest_sprite.layers) do
existing_layer_names[i] = layer.name
end
for _, layer in ipairs(src_sprite.layers) do
if not contains(existing_layer_names, layer.name) then
local new_layer = dest_sprite:newLayer(layer)
new_layer.blendMode = layer.blendMode
new_layer.color = layer.color
new_layer.data = layer.data
new_layer.isCollapsed = layer.isCollapsed
new_layer.isContinuous = layer.isContinuous
new_layer.isEditable = layer.isEditable
new_layer.isVisible = layer.isVisible
new_layer.name = layer.name
new_layer.opacity = layer.opacity
end
end
return dest_sprite
end
--
-- Copy selected cels to new sprite
--
local function copy_cels(src_sprite, dest_sprite, start_frame, end_frame, frame_offset)
-- Index any existing layers by name
local layer_idx = {}
for _, layer in ipairs(dest_sprite.layers) do
layer_idx[layer.name] = layer
end
-- Copy cels
for _, cel in ipairs(src_sprite.cels) do
if cel.frameNumber >= start_frame and cel.frameNumber <= end_frame then
-- Create new frame, if needed
local dest_frame = cel.frameNumber + frame_offset
if dest_frame > #dest_sprite.frames then
dest_sprite:newEmptyFrame()
end
-- Look up layer by name and add new cel
local dest_layer = layer_idx[cel.layer.name]
dest_sprite:newCel(dest_layer, dest_frame, cel.image, cel.position)
end
end
return dest_sprite
end
--
-- Copy tags and associated metadata for selected frames to new sprite
--
local function copy_tags(src_sprite, dest_sprite, start_frame, end_frame, frame_offset)
for _, tag in ipairs(src_sprite.tags) do
local src_start = tag.fromFrame.frameNumber
local src_end = tag.toFrame.frameNumber
if src_start <= end_frame and src_end >= start_frame then
-- Adjust tag frame range to be within selection frame range
local dest_start = math.max(1, src_start + frame_offset)
local dest_end = math.min(#dest_sprite.frames, src_end + frame_offset)
-- Copy tag + metadata to adjusted range
local new_tag = dest_sprite:newTag(dest_start, dest_end)
new_tag.aniDir = tag.aniDir
new_tag.color = tag.color
new_tag.data = tag.data
new_tag.name = tag.name
end
end
return dest_sprite
end
--
-- Run main script from either CLI or GUI
--
local function run()
local src_sprite = get_src_sprite()
if not src_sprite then
return
end
local start_frame = tonumber(app.params['start-frame']) or 1
local end_frame = tonumber(app.params['end-frame']) or #src_sprite.frames
local dest_sprite = nil
-- Gather inputs from GUI, if available
if app.isUIAvailable then
local input_data = get_dialog_inputs(src_sprite)
if not input_data then
return
end
start_frame = input_data.start_frame
end_frame = input_data.end_frame
dest_sprite = get_dest_sprite(src_sprite, input_data.dest_path, input_data.overwrite)
-- Otherwise gather inputs from CLI (or defaults)
else
dest_sprite = get_dest_sprite(src_sprite, nil, nil, start_frame, end_frame)
print('Copying ' .. end_frame - start_frame + 1 .. ' frames')
print(' From: ' .. src_sprite.filename)
print(' To: ' .. dest_sprite.filename)
end
-- If this is an existing sprite, adjust offset by number of existing frames
local frame_offset = -1 * (start_frame - 1)
if #dest_sprite.layers > 0 then
frame_offset = frame_offset + #dest_sprite.frames
end
-- Copy selected data and save new sprite
dest_sprite = copy_layers(src_sprite, dest_sprite)
dest_sprite = fix_first_frame(dest_sprite)
dest_sprite = copy_cels(src_sprite, dest_sprite, start_frame, end_frame, frame_offset)
dest_sprite = copy_tags(src_sprite, dest_sprite, start_frame, end_frame, frame_offset)
dest_sprite:saveAs(dest_sprite.filename)
end
--
-- Initialize plugin, if installed
--
IsPlugin = false
function init(plugin)
IsPlugin = true
plugin.preferences.overwrite = false
plugin:newCommand {
id = 'SplitMerge',
title = 'Split/Merge Frames',
group = 'cel_new',
onclick = run,
onenabled = function()
return app.activeSprite
end
}
end
--
-- Run as a standalone script, if not installed as a plugin
--
if not IsPlugin then
run()
end