Skip to content

Commit 839c5e7

Browse files
committed
Add ability to export only unique frames
1 parent e5c5c0c commit 839c5e7

File tree

2 files changed

+44
-36
lines changed

2 files changed

+44
-36
lines changed

spritesheetExporter/se_ui.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class CommonSettings(QFormLayout):
3737
directory = QLineEdit()
3838
change_dir = QPushButton(KI.icon("folder"), None)
3939
reset_dir = QPushButton(KI.icon("view-refresh"), None)
40+
41+
unique_frames = QCheckBox("Only unique frames")
4042
write_texture_atlas = QCheckBox("Write JSON texture atlas")
4143

4244
def __init__(self):
@@ -61,10 +63,12 @@ def __init__(self):
6163

6264
self.addRow("Export name:", self.name)
6365
self.addRow("Export directory:", dir_layout)
66+
self.addRow(self.unique_frames)
6467
self.addRow(self.write_texture_atlas)
6568

6669
def apply_settings(self, exp: SpritesheetExporter):
6770
exp.export_path = Path(self.directory.text(), self.name.text())
71+
exp.unique_frames = self.unique_frames.isChecked()
6872
exp.write_texture_atlas = self.write_texture_atlas.isChecked()
6973

7074

spritesheetExporter/spritesheet_exporter.py

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from krita import Krita, Document, Node, InfoObject
66
from builtins import Application
7-
from PyQt5.QtCore import QRect
7+
from PyQt5.QtCore import QRect, QByteArray
88

99
from collections.abc import Iterable
1010
from typing import Optional
@@ -19,6 +19,7 @@
1919

2020
class SpritesheetExporter:
2121
export_path = Path.home().joinpath("spritesheet.png")
22+
unique_frames = True
2223

2324
horizontal = True
2425
size = DEFAULT_SPACE
@@ -146,49 +147,58 @@ def _make_frames_dir(self):
146147

147148
return dir
148149

149-
def _copy_frames(self, src: Document, dest: Document) -> int:
150+
def _copy_frames(self, src: Document, dest: Document):
150151
"""
151152
Copies frames from the source document to the destination.
152153
153154
@param src The source document
154155
@param dest The destination document
155-
@returns The number of frames copied
156156
"""
157157

158158
root = dest.rootNode()
159159
width = src.width()
160160
height = src.height()
161161

162+
pixel_set: Optional[set[QByteArray]] = set() if self.unique_frames else None
163+
162164
if self.layers_as_animation:
163165
paint_layers = src.rootNode().findChildNodes("", True, False, "paintlayer")
164166
visible_layers = [i for i in paint_layers if i.visible()]
165167

166168
# export each visible layer
167169
for i, layer in enumerate(visible_layers):
170+
if pixel_set is not None:
171+
pixel_data = layer.pixelData(0, 0, width, height)
172+
if pixel_data in pixel_set:
173+
continue # Got a non-unique frame
174+
pixel_set.add(pixel_data)
175+
168176
clone_layer = dest.createCloneLayer(str(i), layer)
169177
root.addChildNode(clone_layer, None)
178+
else:
179+
if self.end == DEFAULT_TIME or self.start == DEFAULT_TIME:
180+
self._set_frame_times(src)
170181

171-
return len(visible_layers)
172-
173-
if self.end == DEFAULT_TIME or self.start == DEFAULT_TIME:
174-
self._set_frame_times(src)
182+
initial_time = src.currentTime()
183+
frame_range = range(self.start, self.end + 1, self.step)
175184

176-
initial_time = src.currentTime()
177-
frame_range = range(self.start, self.end + 1, self.step)
185+
for i in frame_range:
186+
src.setCurrentTime(i)
178187

179-
for i in frame_range:
180-
src.setCurrentTime(i)
181-
layer = dest.createNode(str(i), "paintlayer")
182-
root.addChildNode(layer, None)
188+
# Ensure the time has been set before copying the pixel data
189+
src.waitForDone()
190+
pixel_data = src.pixelData(0, 0, width, height)
183191

184-
# Ensure the time has been set before copying the pixel data
185-
src.waitForDone()
186-
pixel_data = src.pixelData(0, 0, width, height)
187-
layer.setPixelData(pixel_data, 0, 0, width, height)
192+
if pixel_set is not None:
193+
if pixel_data in pixel_set:
194+
continue # Got a non-unique frame
195+
pixel_set.add(pixel_data)
188196

189-
src.setCurrentTime(initial_time) # reset time
197+
layer = dest.createNode(str(i), "paintlayer")
198+
layer.setPixelData(pixel_data, 0, 0, width, height)
199+
root.addChildNode(layer, None)
190200

191-
return len(frame_range)
201+
src.setCurrentTime(initial_time) # reset time
192202

193203
def _process_frames(self, src: Document, dest: Document):
194204
width = src.width()
@@ -197,12 +207,10 @@ def _process_frames(self, src: Document, dest: Document):
197207
frames_dir = self._make_frames_dir() if self.export_frame_sequence else None
198208
texture_atlas = {"frames": []} if self.write_texture_atlas else None
199209

200-
for layer in dest.rootNode().childNodes():
201-
name = layer.name()
202-
210+
for i, layer in enumerate(dest.rootNode().childNodes()):
203211
if frames_dir is not None:
204212
file_name = "".join(
205-
[self.base_name, name.zfill(3), self.export_path.suffix]
213+
[self.base_name, str(i).zfill(3), self.export_path.suffix]
206214
)
207215
layer.save(
208216
str(frames_dir.joinpath(file_name)),
@@ -212,17 +220,13 @@ def _process_frames(self, src: Document, dest: Document):
212220
QRect(0, 0, width, height),
213221
)
214222

215-
if self.layers_as_animation:
216-
index = int(name)
217-
else:
218-
index = (int(name) - self.start) // self.step
219-
223+
# Layers are ordered by when they were added, so using `i` is fine
220224
if self.horizontal:
221-
x_pos = (index % self.size) * width
222-
y_pos = (index // self.size) * height
225+
x_pos = (i % self.size) * width
226+
y_pos = (i // self.size) * height
223227
else:
224-
x_pos = (index // self.size) * width
225-
y_pos = (index % self.size) * height
228+
x_pos = (i // self.size) * width
229+
y_pos = (i % self.size) * height
226230

227231
layer.move(x_pos, y_pos)
228232

@@ -275,7 +279,10 @@ def export(self, debug=False):
275279
)
276280

277281
sheet.setFileName(str(self.export_path))
278-
num_frames = self._copy_frames(doc, sheet)
282+
sheet.rootNode().setChildNodes([]) # Remove any default layers
283+
284+
self._copy_frames(doc, sheet)
285+
num_frames = len(sheet.rootNode().childNodes())
279286

280287
if self.size == DEFAULT_SPACE:
281288
# Pack the sprites as densely as possible with a square fit
@@ -301,9 +308,6 @@ def export(self, debug=False):
301308
+ f"new doc width: {sheet.width()}"
302309
)
303310

304-
# Remove the default Background layer
305-
sheet.rootNode().childNodes()[0].remove()
306-
307311
# Position frames, and optionally write JSON or export all frames
308312
self._process_frames(doc, sheet)
309313

0 commit comments

Comments
 (0)