1
0
mirror of https://github.com/TheFunny/ArisuAutoSweeper synced 2026-01-07 14:05:12 +00:00

Compare commits

...

9 Commits

7 changed files with 159 additions and 51 deletions

View File

@ -179,20 +179,29 @@ def iter_assets():
if image.attr != '':
row = deep_get(data, keys=[image.module, image.assets, image.server, image.frame])
row.load_image(image)
# Apply `search` of the first frame to all
# Set `search`
for path, frames in deep_iter(data, depth=3):
print(path, frames)
# If `search` attribute is set in the first frame, apply to all
first = frames[1]
search = first.search if first.search else DataAssets.area_to_search(first.area)
for frame in frames.values():
frame.search = search
if first.search:
for frame in frames.values():
frame.search = first.search
else:
for frame in frames.values():
if frame.search:
# Follow frame specific `search`
pass
else:
# Generate `search` from `area`
frame.search = DataAssets.area_to_search(frame.area)
return data
def generate_code():
all = iter_assets()
for module, module_data in all.items():
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):
@ -204,7 +213,7 @@ def generate_code():
continue
os.remove(prev)
for module, module_data in all.items():
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()

View File

@ -74,7 +74,7 @@ class Button(Resource):
threshold=threshold
)
def match_template(self, image, similarity=0.85) -> bool:
def match_template(self, image, similarity=0.85, direct_match=False) -> bool:
"""
Detects assets by template matching.
@ -83,18 +83,45 @@ class Button(Resource):
Args:
image: Screenshot.
similarity (float): 0-1.
direct_match: True to ignore `self.search`
Returns:
bool.
"""
image = crop(image, self.search, copy=False)
if not direct_match:
image = crop(image, self.search, copy=False)
res = cv2.matchTemplate(self.image, image, cv2.TM_CCOEFF_NORMED)
_, sim, _, point = cv2.minMaxLoc(res)
self._button_offset = np.array(point) + self.search[:2] - self.area[:2]
return sim > similarity
def match_template_color(self, image, similarity=0.85, threshold=30) -> bool:
def match_multi_template(self, image, similarity=0.85, direct_match=False):
"""
Detects assets by template matching, return multiple reults
Args:
image: Screenshot.
similarity (float): 0-1.
direct_match: True to ignore `self.search`
Returns:
list:
"""
if not direct_match:
image = crop(image, self.search, copy=False)
res = cv2.matchTemplate(self.image, image, cv2.TM_CCOEFF_NORMED)
res = cv2.inRange(res, similarity, 1.)
try:
points = np.array(cv2.findNonZero(res))[:, 0, :]
points += self.search[:2]
return points.tolist()
except IndexError:
# Empty result
# IndexError: too many indices for array: array is 0-dimensional, but 3 were indexed
return []
def match_template_color(self, image, similarity=0.85, threshold=30, direct_match=False) -> bool:
"""
Template match first, color match then
@ -102,11 +129,12 @@ class Button(Resource):
image: Screenshot.
similarity (float): 0-1.
threshold (int): Default to 10.
direct_match: True to ignore `self.search`
Returns:
bool.
"""
matched = self.match_template(image, similarity=similarity)
matched = self.match_template(image, similarity=similarity, direct_match=direct_match)
if not matched:
return False
@ -124,10 +152,10 @@ class ButtonWrapper(Resource):
self.name = name
self.data_buttons = kwargs
self._matched_button: t.Optional[Button] = None
self.resource_add(self.name)
self.resource_add(f'{name}:{next(self.iter_buttons(), None)}')
def resource_release(self):
del_cached_property(self, 'assets')
del_cached_property(self, 'buttons')
self._matched_button = None
def __str__(self):
@ -144,16 +172,25 @@ class ButtonWrapper(Resource):
def __bool__(self):
return True
def iter_buttons(self) -> t.Iterator[Button]:
for _, assets in self.data_buttons.items():
if isinstance(assets, Button):
yield assets
elif isinstance(assets, list):
for asset in assets:
yield asset
@cached_property
def buttons(self) -> t.List[Button]:
# for trial in [server.lang, 'share', 'cn']:
for trial in [server.lang, 'share', 'jp']:
assets = self.data_buttons.get(trial, None)
if assets is not None:
for trial in [server.lang, 'share', 'cn']:
try:
assets = self.data_buttons[trial]
if isinstance(assets, Button):
return [assets]
elif isinstance(assets, list):
return assets
except KeyError:
pass
raise ScriptError(f'ButtonWrapper({self}) on server {server.lang} has no fallback button')
@ -164,16 +201,45 @@ class ButtonWrapper(Resource):
return True
return False
def match_template(self, image, similarity=0.85) -> bool:
def match_template(self, image, similarity=0.85, direct_match=False) -> bool:
for assets in self.buttons:
if assets.match_template(image, similarity=similarity):
if assets.match_template(image, similarity=similarity, direct_match=direct_match):
self._matched_button = assets
return True
return False
def match_template_color(self, image, similarity=0.85, threshold=30) -> bool:
def match_multi_template(self, image, similarity=0.85, threshold=5, direct_match=False):
"""
Detects assets by template matching, return multiple results
Args:
image: Screenshot.
similarity (float): 0-1.
threshold:
direct_match: True to ignore `self.search`
Returns:
list[ClickButton]:
"""
ps = []
for assets in self.buttons:
if assets.match_template_color(image, similarity=similarity, threshold=threshold):
ps += assets.match_multi_template(image, similarity=similarity, direct_match=direct_match)
if not ps:
return []
from module.base.utils.points import Points
ps = Points(ps).group(threshold=threshold)
area_list = [area_offset(self.area, p - self.area[:2]) for p in ps]
button_list = [area_offset(self.button, p - self.area[:2]) for p in ps]
return [
ClickButton(area=info[0], button=info[1], name=f'{self.name}_result{i}')
for i, info in enumerate(zip(area_list, button_list))
]
def match_template_color(self, image, similarity=0.85, threshold=30, direct_match=False) -> bool:
for assets in self.buttons:
if assets.match_template_color(
image, similarity=similarity, threshold=threshold, direct_match=direct_match):
self._matched_button = assets
return True
return False
@ -222,18 +288,32 @@ class ButtonWrapper(Resource):
"""
if isinstance(button, ButtonWrapper):
button = button.matched_button
for b in self.buttons:
for b in self.iter_buttons():
b.load_offset(button)
def clear_offset(self):
for b in self.buttons:
for b in self.iter_buttons():
b.clear_offset()
def load_search(self, area):
"""
Set `search` attribute.
Note that this method is irreversible.
Args:
area:
"""
for b in self.iter_buttons():
b.search = area
class ClickButton:
def __init__(self, button, name='CLICK_BUTTON'):
self.area = button
self.button = button
def __init__(self, area, button=None, name='CLICK_BUTTON'):
self.area = area
if button is None:
self.button = area
else:
self.button = button
self.name = name
def __str__(self):
@ -265,4 +345,4 @@ def match_template(image, template, similarity=0.85):
"""
res = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
_, sim, _, point = cv2.minMaxLoc(res)
return sim > similarity
return sim > similarity

View File

@ -1,11 +1,11 @@
import re
import module.config.server as server
from module.base.decorator import cached_property, del_cached_property
from module.base.decorator import cached_property
def get_assets_from_file(file, regex):
def get_assets_from_file(file):
assets = set()
regex = re.compile(r"file='(.*?)'")
with open(file, 'r', encoding='utf-8') as f:
for row in f.readlines():
result = regex.search(row)
@ -20,11 +20,9 @@ class PreservedAssets:
assets = set()
assets |= get_assets_from_file(
file='./tasks/base/assets/assets_base_page.py',
regex=re.compile(r'^([A-Za-z][A-Za-z0-9_]+) = ')
)
assets |= get_assets_from_file(
file='./tasks/base/assets/assets_base_popup.py',
regex=re.compile(r'^([A-Za-z][A-Za-z0-9_]+) = ')
)
return assets
@ -44,11 +42,13 @@ class Resource:
@classmethod
def is_loaded(cls, obj):
if hasattr(obj, '_image') and obj._image is None:
return False
elif hasattr(obj, 'image') and obj.image is None:
return False
return True
if hasattr(obj, '_image') and obj._image is not None:
return True
if hasattr(obj, 'image') and obj.image is not None:
return True
if hasattr(obj, 'buttons') and obj.buttons is not None:
return True
return False
@classmethod
def resource_show(cls):
@ -56,11 +56,16 @@ class Resource:
logger.hr('Show resource')
for key, obj in cls.instances.items():
if cls.is_loaded(obj):
continue
logger.info(f'{obj}: {key}')
logger.info(f'{obj}: {key}')
def release_resources(next_task=''):
# Release all OCR models
# det models take 400MB
if not next_task:
from module.ocr.models import OCR_MODEL
OCR_MODEL.resource_release()
# Release assets cache
# module.ui has about 80 assets and takes about 3MB
# Alas has about 800 assets, but they are not all loaded.
@ -74,4 +79,4 @@ def release_resources(next_task=''):
obj.resource_release()
# Useless in most cases, but just call it
# gc.collect()
# gc.collect()

View File

@ -458,7 +458,8 @@ class ConfigUpdater:
value = deep_get(old, keys=keys, default=data['value'])
typ = data['type']
display = data.get('display')
if is_template or value is None or value == '' or typ == 'lock' or (display == 'hide' and typ != 'stored'):
if (is_template or value is None or value == ''
or typ in ['lock', 'state'] or (display == 'hide' and typ != 'stored')):
value = data['value']
value = parse_value(value, data=data)
deep_set(new, keys=keys, value=value)

View File

@ -1,6 +1,6 @@
from pponnxcr import TextSystem as TextSystem_
from module.base.decorator import cached_property
from module.base.decorator import cached_property, del_cached_property
from module.exception import ScriptError
DIC_LANG_TO_MODEL = {
@ -56,6 +56,12 @@ class OcrModel:
except AttributeError:
raise ScriptError(f'OCR model under lang "{lang}" does not exists')
def resource_release(self):
del_cached_property(self, 'zhs')
del_cached_property(self, 'en')
del_cached_property(self, 'ja')
del_cached_property(self, 'zht')
@cached_property
def zhs(self):
return TextSystem('zhs')

View File

@ -89,12 +89,15 @@ def readable_time(before: str) -> str:
elif diff < 60:
# < 1 min
return t("Gui.Dashboard.JustNow")
elif diff < 3600:
elif diff < 5400:
# < 90 min
return t("Gui.Dashboard.MinutesAgo", time=int(diff // 60))
elif diff < 86400:
elif diff < 129600:
# < 36 hours
return t("Gui.Dashboard.HoursAgo", time=int(diff // 3600))
elif diff < 1296000:
# < 15 days
return t("Gui.Dashboard.DaysAgo", time=int(diff // 86400))
else:
# > 15 days
# >= 15 days
return t("Gui.Dashboard.LongTimeAgo")

View File

@ -9,17 +9,17 @@ from queue import Queue
from typing import Callable, Generator, List
import pywebio
from module.config.utils import deep_iter
from module.logger import logger
from module.webui.setting import State
from pywebio.input import PASSWORD, input
from pywebio.output import PopupSize, popup, put_html, toast
from pywebio.session import eval_js
from pywebio.session import info as session_info
from pywebio.session import register_thread, run_js
from rich.console import Console, ConsoleOptions
from rich.console import Console
from rich.terminal_theme import TerminalTheme
from module.config.utils import deep_iter
from module.logger import logger
from module.webui.setting import State
RE_DATETIME = (
r"\d{4}\-(0\d|1[0-2])\-([0-2]\d|[3][0-1]) "
@ -455,7 +455,11 @@ def get_localstorage(key):
def re_fullmatch(pattern, string):
if pattern == "datetime":
pattern = RE_DATETIME
try:
datetime.datetime.fromisoformat(string)
return True
except ValueError:
return False
# elif:
return re.fullmatch(pattern=pattern, string=string)