forked from LmeSzinc/StarRailCopilot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
button_extract.py
270 lines (238 loc) · 10.2 KB
/
button_extract.py
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
import os
import re
import typing as t
from dataclasses import dataclass
import numpy as np
from tqdm import tqdm
from module.base.code_generator import CodeGenerator
from module.base.utils import SelectedGrids, area_limit, area_pad, get_bbox, get_color, image_size, load_image
from module.config.config_manual import ManualConfig as AzurLaneConfig
from module.config.server import VALID_LANG
from module.config.utils import deep_get, deep_iter, deep_set, iter_folder
from module.logger import logger
SHARE_SERVER = 'share'
ASSET_SERVER = [SHARE_SERVER] + VALID_LANG
class AssetsImage:
REGEX_ASSETS = re.compile(
f'^{AzurLaneConfig.ASSETS_FOLDER}/'
f'(?P<server>{"|".join(ASSET_SERVER).lower()})/'
f'(?P<module>[a-zA-Z0-9_/]+?)/'
f'(?P<assets>\w+)'
f'(?P<frame>\.\d+)?'
f'(?P<attr>\.AREA|\.SEARCH|\.COLOR|\.BUTTON)?'
f'\.png$'
)
def __init__(self, file: str):
"""
Args:
file: ./assets/<server>/<module>/<assets>.<frame>.<attr>.png
Example: ./assets/cn/ui/login/LOGIN_CONFIRM.2.BUTTON.png
then, server="cn", module="ui/login", assets="LOGIN_CONFIRM", frame=2, attr="BUTTON"
<frame> and <attr> are optional.
"""
self.file: str = file
prefix = AzurLaneConfig.ASSETS_FOLDER
res = AssetsImage.REGEX_ASSETS.match(file)
self.valid = False
self.server = ''
self.module = ''
self.assets = ''
self.frame = 1
self.attr = ''
if res:
self.valid = True
self.server = res.group('server')
self.module = res.group('module')
self.assets = res.group('assets')
if res.group('frame'):
self.frame = int(res.group('frame').strip('.'))
else:
self.frame = 1
if res.group('attr'):
self.attr = res.group('attr').strip('.')
else:
self.attr = ''
self.parent_file = f'{prefix}{res.group(1)}.png'
else:
logger.info(f'Invalid assets name: {self.file}')
self.bbox: t.Tuple = ()
self.mean: t.Tuple = ()
def parse(self):
image = load_image(self.file)
size = image_size(image)
if size != AzurLaneConfig.ASSETS_RESOLUTION:
logger.warning(f'{self.file} has wrong resolution: {size}')
self.valid = False
bbox = get_bbox(image)
mean = get_color(image=image, area=bbox)
mean = tuple(np.rint(mean).astype(int))
self.bbox = bbox
self.mean = mean
return bbox, mean
def __str__(self):
if self.valid:
return f'AssetsImage(module={self.module}, assets={self.assets}, server={self.server}, frame={self.frame}, attr={self.attr})'
else:
return f'AssetsImage(file={self.file}, valid={self.valid})'
def iter_images():
for server in ASSET_SERVER:
for path, folders, files in os.walk(os.path.join(AzurLaneConfig.ASSETS_FOLDER, server)):
for file in files:
if not file.startswith('.'):
file = os.path.join(path, file).replace('\\', '/')
yield AssetsImage(file)
@dataclass
class DataAssets:
module: str
assets: str
server: str
frame: int
file: str = ''
area: t.Tuple[int, int, int, int] = ()
search: t.Tuple[int, int, int, int] = ()
color: t.Tuple[int, int, int] = ()
button: t.Tuple[int, int, int, int] = ()
has_raw_area = False
has_raw_search = False
has_raw_color = False
has_raw_button = False
@staticmethod
def area_to_search(area):
area = area_pad(area, pad=-20)
area = area_limit(area, (0, 0, *AzurLaneConfig.ASSETS_RESOLUTION))
return area
@classmethod
def product(cls, image: AssetsImage):
"""
Product DataAssets from AssetsImage with attr=""
"""
data = cls(module=image.module, assets=image.assets, server=image.server, frame=image.frame, file=image.file)
data.load_image(image)
return data
def load_image(self, image: AssetsImage):
if image.attr == '':
self.file = image.file
self.area = image.bbox
self.color = image.mean
self.button = image.bbox
elif image.attr == 'AREA':
self.area = image.bbox
self.has_raw_area = True
elif image.attr == 'SEARCH':
self.search = image.bbox
self.has_raw_search = True
elif image.attr == 'COLOR':
self.color = image.mean
self.has_raw_color = True
elif image.attr == 'BUTTON':
self.button = image.bbox
self.has_raw_button = True
else:
logger.warning(f'Trying to load an image with unknown attribute: {image}')
def generate_code(self):
return f'Assets(file="{self.file}", area={self.area}, search={self.search}, color={self.color}, button={self.button})'
def iter_assets():
images = list(iter_images())
# parse images, this may take a while
for image in tqdm(images):
image.parse()
# Validate images
images = SelectedGrids(images).select(valid=True)
images.create_index('module', 'assets', 'server', 'frame', 'attr')
for image in images.filter(lambda x: bool(x.attr)):
image: AssetsImage = image
if not images.indexed_select(image.module, image.assets, image.server, image.frame, ''):
logger.warning(f'Attribute assets has no parent assets: {image.file}')
image.valid = False
if not images.indexed_select(image.module, image.assets, image.server, 1, ''):
logger.warning(f'Attribute assets has no first frame: {image.file}')
image.valid = False
if image.attr == 'SEARCH' and image.frame > 1:
logger.warning(f'Attribute SEARCH with frame > 1 is not allowed: {image.file}')
image.valid = False
images = images.select(valid=True).sort('module', 'assets', 'server', 'frame')
# Convert to DataAssets
data = {}
for image in images:
if image.attr == '':
row = DataAssets.product(image)
row.load_image(image)
deep_set(data, keys=[image.module, image.assets, image.server, image.frame], value=row)
# Load attribute images
for image in images:
if image.attr != '':
row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame])
row.load_image(image)
# Set `search`
for path, frames in deep_iter(data, depth=3):
print(path, frames)
for frame in frames.values():
# Generate `search` from `area`
if not frame.has_raw_search:
frame.search = DataAssets.area_to_search(frame.area)
# If an attribute is set in the first frame, apply to all
first: DataAssets = frames[1]
for frame in frames.values():
# frame: DataAssets = frame
if not frame.has_raw_area and first.has_raw_area:
frame.area = first.area
if not frame.has_raw_search and first.has_raw_search:
frame.search = first.search
if not frame.has_raw_color and first.has_raw_color:
frame.color = first.color
if not frame.has_raw_button and first.has_raw_button:
frame.button = first.button
return data
def generate_code():
all_assets = iter_assets()
for module, module_data in all_assets.items():
path = os.path.join(AzurLaneConfig.ASSETS_MODULE, module.split('/', maxsplit=1)[0])
output = os.path.join(path, 'assets.py')
if os.path.exists(output):
os.remove(output)
output = os.path.join(path, 'assets')
os.makedirs(output, exist_ok=True)
for prev in iter_folder(output, ext='.py'):
if os.path.basename(prev) == '__init__.py':
continue
os.remove(prev)
for module, module_data in all_assets.items():
path = os.path.join(AzurLaneConfig.ASSETS_MODULE, module.split('/', maxsplit=1)[0])
output = os.path.join(path, 'assets')
gen = CodeGenerator()
gen.Import("""
from module.base.button import Button, ButtonWrapper
""")
gen.CommentAutoGenerage('dev_tools.button_extract')
for assets, assets_data in module_data.items():
has_share = SHARE_SERVER in assets_data
with gen.Object(key=assets, object_class='ButtonWrapper'):
gen.ObjectAttr(key='name', value=assets)
if has_share:
servers = assets_data.keys()
else:
servers = VALID_LANG
for server in servers:
frames = list(assets_data.get(server, {}).values())
if len(frames) > 1:
with gen.ObjectAttr(key=server, value=gen.List()):
for index, frame in enumerate(frames):
with gen.ListItem(gen.Object(object_class='Button')):
gen.ObjectAttr(key='file', value=frame.file)
gen.ObjectAttr(key='area', value=frame.area)
gen.ObjectAttr(key='search', value=frame.search)
gen.ObjectAttr(key='color', value=frame.color)
gen.ObjectAttr(key='button', value=frame.button)
elif len(frames) == 1:
frame = frames[0]
with gen.ObjectAttr(key=server, value=gen.Object(object_class='Button')):
gen.ObjectAttr(key='file', value=frame.file)
gen.ObjectAttr(key='area', value=frame.area)
gen.ObjectAttr(key='search', value=frame.search)
gen.ObjectAttr(key='color', value=frame.color)
gen.ObjectAttr(key='button', value=frame.button)
else:
gen.ObjectAttr(key=server, value=None)
gen.write(os.path.join(output, f'assets_{module.replace("/", "_")}.py'))
if __name__ == '__main__':
generate_code()