localdex 0.1.20__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localdex/__init__.py +0 -1
- localdex/core.py +4 -46
- {localdex-0.1.20.dist-info → localdex-0.2.2.dist-info}/METADATA +1 -4
- {localdex-0.1.20.dist-info → localdex-0.2.2.dist-info}/RECORD +7 -8
- localdex/sprite_downloader.py +0 -932
- {localdex-0.1.20.dist-info → localdex-0.2.2.dist-info}/WHEEL +0 -0
- {localdex-0.1.20.dist-info → localdex-0.2.2.dist-info}/entry_points.txt +0 -0
- {localdex-0.1.20.dist-info → localdex-0.2.2.dist-info}/top_level.txt +0 -0
localdex/__init__.py
CHANGED
@@ -3,7 +3,6 @@ from .core import LocalDex
|
|
3
3
|
from .models import Pokemon, Move, Ability, Item, BaseStats
|
4
4
|
from .exceptions import PokemonNotFoundError, MoveNotFoundError, AbilityNotFoundError, ItemNotFoundError
|
5
5
|
from .random_battles import RandomBattleSets
|
6
|
-
from .sprite_downloader import SpriteDownloader
|
7
6
|
|
8
7
|
__all__ = [
|
9
8
|
"LocalDex",
|
localdex/core.py
CHANGED
@@ -19,7 +19,6 @@ from .exceptions import (
|
|
19
19
|
)
|
20
20
|
from .data_loader import DataLoader
|
21
21
|
from .random_battles import RandomBattleSets
|
22
|
-
from .sprite_downloader import SpriteDownloader
|
23
22
|
|
24
23
|
|
25
24
|
class LocalDex:
|
@@ -31,7 +30,7 @@ class LocalDex:
|
|
31
30
|
performance and comprehensive search capabilities.
|
32
31
|
"""
|
33
32
|
|
34
|
-
def __init__(self, data_path: Optional[str] = None, data_dir: Optional[str] = None, enable_caching: bool = True, enable_random_battles: bool = True
|
33
|
+
def __init__(self, data_path: Optional[str] = None, data_dir: Optional[str] = None, enable_caching: bool = True, enable_random_battles: bool = True):
|
35
34
|
"""
|
36
35
|
Initialize the LocalDex.
|
37
36
|
|
@@ -40,17 +39,14 @@ class LocalDex:
|
|
40
39
|
data_dir: Alias for data_path for backward compatibility.
|
41
40
|
enable_caching: Whether to enable caching for better performance.
|
42
41
|
enable_random_battles: Whether to enable random battle sets functionality.
|
43
|
-
|
44
|
-
sprite_max_workers: Maximum number of worker threads for parallel sprite processing.
|
42
|
+
|
45
43
|
"""
|
46
44
|
# Use data_dir if provided, otherwise use data_path
|
47
45
|
final_data_path = data_dir if data_dir is not None else data_path
|
48
46
|
self.data_loader = DataLoader(final_data_path)
|
49
47
|
self.data_dir = final_data_path # Store for backward compatibility
|
50
48
|
self.enable_caching = enable_caching
|
51
|
-
|
52
|
-
# Sprite downloader integration
|
53
|
-
self.sprite_downloader = SpriteDownloader(final_data_path, max_workers=sprite_max_workers) if enable_sprite_downloader else None
|
49
|
+
|
54
50
|
|
55
51
|
# Initialize random battle sets
|
56
52
|
self.random_battles = RandomBattleSets() if enable_random_battles else None
|
@@ -703,42 +699,4 @@ class LocalDex:
|
|
703
699
|
if self.random_battles is not None:
|
704
700
|
self.random_battles.cleanup_downloads()
|
705
701
|
|
706
|
-
|
707
|
-
"""
|
708
|
-
Download and extract all sprites (Pokemon and items).
|
709
|
-
"""
|
710
|
-
if not self.sprite_downloader:
|
711
|
-
raise RuntimeError("SpriteDownloader is not enabled.")
|
712
|
-
return self.sprite_downloader.download_all_sprites()
|
713
|
-
|
714
|
-
def get_pokemon_sprite_path(self, pokemon_name: str):
|
715
|
-
"""
|
716
|
-
Get the path to a Pokemon sprite image.
|
717
|
-
"""
|
718
|
-
if not self.sprite_downloader:
|
719
|
-
raise RuntimeError("SpriteDownloader is not enabled.")
|
720
|
-
return self.sprite_downloader.get_pokemon_sprite_path(pokemon_name)
|
721
|
-
|
722
|
-
def get_item_sprite_path(self, item_name: str):
|
723
|
-
"""
|
724
|
-
Get the path to an item sprite image.
|
725
|
-
"""
|
726
|
-
if not self.sprite_downloader:
|
727
|
-
raise RuntimeError("SpriteDownloader is not enabled.")
|
728
|
-
return self.sprite_downloader.get_item_sprite_path(item_name)
|
729
|
-
|
730
|
-
def get_sprite_metadata(self, sprite_type: str = "pokemon"):
|
731
|
-
"""
|
732
|
-
Get sprite metadata for either 'pokemon' or 'items'.
|
733
|
-
"""
|
734
|
-
if not self.sprite_downloader:
|
735
|
-
raise RuntimeError("SpriteDownloader is not enabled.")
|
736
|
-
return self.sprite_downloader.get_sprite_metadata(sprite_type)
|
737
|
-
|
738
|
-
def list_available_sprites(self, sprite_type: str = "pokemon"):
|
739
|
-
"""
|
740
|
-
List all available sprites of a given type ('pokemon' or 'items').
|
741
|
-
"""
|
742
|
-
if not self.sprite_downloader:
|
743
|
-
raise RuntimeError("SpriteDownloader is not enabled.")
|
744
|
-
return self.sprite_downloader.list_available_sprites(sprite_type)
|
702
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: localdex
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.2
|
4
4
|
Summary: A local Pokemon data repository/Pokedex with fast offline access
|
5
5
|
Author-email: LocalDex Team <localdex@example.com>
|
6
6
|
License: MIT
|
@@ -22,7 +22,6 @@ Requires-Python: >=3.8
|
|
22
22
|
Description-Content-Type: text/markdown
|
23
23
|
Requires-Dist: requests
|
24
24
|
Requires-Dist: typing-extensions
|
25
|
-
Requires-Dist: pillow
|
26
25
|
Provides-Extra: core
|
27
26
|
Provides-Extra: gen1
|
28
27
|
Provides-Extra: gen2
|
@@ -47,8 +46,6 @@ Requires-Dist: black>=22.0.0; extra == "dev"
|
|
47
46
|
Requires-Dist: isort>=5.0.0; extra == "dev"
|
48
47
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
49
48
|
Requires-Dist: flake8>=5.0.0; extra == "dev"
|
50
|
-
Requires-Dist: pillow>=9.0.0; extra == "dev"
|
51
|
-
Requires-Dist: selenium>=4.0.0; extra == "dev"
|
52
49
|
|
53
50
|
# LocalDex
|
54
51
|
|
@@ -1,11 +1,10 @@
|
|
1
|
-
localdex/__init__.py,sha256=
|
1
|
+
localdex/__init__.py,sha256=cJMmDgmw3d16WB3d1lR4-j-h7Bp8syqrWWTfYXsBB28,496
|
2
2
|
localdex/cli.py,sha256=WqBCyA0akAFJNrYa8jCA3zoZgYEptDYDMGfAGkvKyZc,19317
|
3
|
-
localdex/core.py,sha256=
|
3
|
+
localdex/core.py,sha256=hhQETVgy0lIRfeou3lolgZ7ay1RZaEbrM9h8xkwLVVc,26733
|
4
4
|
localdex/data_loader.py,sha256=hi9aSTto5Ti-OBGOgrQ-XwD5hmivsUwS1uC4rulhwQI,11366
|
5
5
|
localdex/download_data.py,sha256=ibAHDxL60sV4LVN9isCmf8vvd_aI9IQbyjJpU0FHGUo,18869
|
6
6
|
localdex/exceptions.py,sha256=Z02-8Kci6jFDk2nnGdVSHZJMDDWE9vuwuASs4VM3To8,2777
|
7
7
|
localdex/random_battles.py,sha256=xRGQ6v-g9NG1-4jcQJWHOfskNUv-tFXspCXPRdctdmQ,9213
|
8
|
-
localdex/sprite_downloader.py,sha256=q4ropKb6vCZmAHbetIXLOzcdbk50oa53Vv_WiiXbOk8,41379
|
9
8
|
localdex/data/abilities/adaptability.json,sha256=FGEEZmL80YVcIXHV-h287Wu-UJycM1QEJxiOHK2I4mY,289
|
10
9
|
localdex/data/abilities/aerilate.json,sha256=KRZOVH2KGdEQ_3UktFoXoagUOaC8jIPZ6Ti-YGzW44s,301
|
11
10
|
localdex/data/abilities/aftermath.json,sha256=Awv_8jquu3pM4CemD5ylS1v83ABrqGtrgMEkL-1ldBk,285
|
@@ -4792,8 +4791,8 @@ localdex/models/ability.py,sha256=AQzv3XUHHl4sustMJjPDDjJOjXu2GMLTfcM3-tqQ_1w,30
|
|
4792
4791
|
localdex/models/item.py,sha256=zXao8F-jBPUGq_YLeGeYeK_dZVI7aZMXtWOPwR3qusY,4677
|
4793
4792
|
localdex/models/move.py,sha256=hfgcWI4ziz5MMvc9ddmkotxzYYdrSUqZZQ72IU5tucs,7629
|
4794
4793
|
localdex/models/pokemon.py,sha256=v5zkxY1NGGR-MczFCZe9fHt6u_BAqAjMiQZlpcLXVGc,5408
|
4795
|
-
localdex-0.
|
4796
|
-
localdex-0.
|
4797
|
-
localdex-0.
|
4798
|
-
localdex-0.
|
4799
|
-
localdex-0.
|
4794
|
+
localdex-0.2.2.dist-info/METADATA,sha256=MTmMmKoEZuK8-P6-_JTbwkGw7s4kRDcVySb7nzjQ8iQ,13994
|
4795
|
+
localdex-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
4796
|
+
localdex-0.2.2.dist-info/entry_points.txt,sha256=n5GxSeQo-MRuvrT2wVk7hOzEFFsWf6tkBjkzmGIYJe4,47
|
4797
|
+
localdex-0.2.2.dist-info/top_level.txt,sha256=vtupDMH-IaxVCoEZrmE0QzdTwhaKzngVJbTA1NkR_MY,9
|
4798
|
+
localdex-0.2.2.dist-info/RECORD,,
|
localdex/sprite_downloader.py
DELETED
@@ -1,932 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Sprite downloader for LocalDex.
|
3
|
-
|
4
|
-
This module handles downloading and managing Pokemon and item sprites
|
5
|
-
from Pokemon Showdown and other sources.
|
6
|
-
"""
|
7
|
-
|
8
|
-
import os
|
9
|
-
import sys
|
10
|
-
import re
|
11
|
-
import json
|
12
|
-
import glob
|
13
|
-
import shutil
|
14
|
-
import subprocess
|
15
|
-
import tempfile
|
16
|
-
import urllib.request
|
17
|
-
import zipfile
|
18
|
-
import concurrent.futures
|
19
|
-
import threading
|
20
|
-
import requests
|
21
|
-
import time
|
22
|
-
from pathlib import Path
|
23
|
-
from typing import Dict, List, Any, Tuple, Optional, Set, Union
|
24
|
-
from PIL import Image
|
25
|
-
from selenium import webdriver
|
26
|
-
from selenium.webdriver.chrome.options import Options
|
27
|
-
from selenium.webdriver.chrome.service import Service
|
28
|
-
from selenium.webdriver.common.by import By
|
29
|
-
from selenium.webdriver.support.ui import WebDriverWait
|
30
|
-
from selenium.webdriver.support import expected_conditions as EC
|
31
|
-
|
32
|
-
from .exceptions import DataLoadError
|
33
|
-
|
34
|
-
|
35
|
-
class SpriteDownloader:
|
36
|
-
"""
|
37
|
-
Downloads and manages Pokemon and item sprites.
|
38
|
-
|
39
|
-
This class handles downloading spritesheets from Pokemon Showdown,
|
40
|
-
extracting individual sprites, and managing sprite metadata.
|
41
|
-
"""
|
42
|
-
|
43
|
-
def __init__(self, data_dir: Optional[str] = None, enable_progress_output: bool = False, max_workers: int = 8):
|
44
|
-
"""
|
45
|
-
Initialize the sprite downloader.
|
46
|
-
|
47
|
-
Args:
|
48
|
-
data_dir: Directory to save sprites and metadata. If None, uses package data.
|
49
|
-
enable_progress_output: Whether to output progress messages.
|
50
|
-
max_workers: Maximum number of worker threads for parallel processing.
|
51
|
-
"""
|
52
|
-
if data_dir is None:
|
53
|
-
# Use package data directory
|
54
|
-
package_dir = Path(__file__).parent
|
55
|
-
self.data_dir = package_dir / "data"
|
56
|
-
else:
|
57
|
-
self.data_dir = Path(data_dir)
|
58
|
-
|
59
|
-
# Create sprite directories
|
60
|
-
self.sprites_dir = self.data_dir / "sprites"
|
61
|
-
self.spritesheet_dir = self.data_dir / "spritesheets"
|
62
|
-
self.pokemon_sprites_dir = self.sprites_dir / "pokemon"
|
63
|
-
self.item_sprites_dir = self.sprites_dir / "items"
|
64
|
-
|
65
|
-
for directory in [self.sprites_dir, self.spritesheet_dir,
|
66
|
-
self.pokemon_sprites_dir, self.item_sprites_dir]:
|
67
|
-
directory.mkdir(parents=True, exist_ok=True)
|
68
|
-
|
69
|
-
self.enable_progress_output = enable_progress_output
|
70
|
-
self.max_workers = max_workers
|
71
|
-
|
72
|
-
# Metadata files
|
73
|
-
self.pokemon_coords_file = self.spritesheet_dir / "pokemon_sprite_coords.json"
|
74
|
-
self.item_coords_file = self.spritesheet_dir / "item_sprite_coords.json"
|
75
|
-
self.pokemon_metadata_file = self.spritesheet_dir / "pokemon_sprite_metadata.json"
|
76
|
-
self.item_metadata_file = self.spritesheet_dir / "item_sprite_metadata.json"
|
77
|
-
|
78
|
-
def _update_progress(self, progress: float, message: str = ""):
|
79
|
-
"""Update progress output."""
|
80
|
-
if self.enable_progress_output:
|
81
|
-
print(f"PROGRESS:{progress}:{message}", flush=True)
|
82
|
-
else:
|
83
|
-
print(f"Progress: {progress*100:.1f}% - {message}")
|
84
|
-
|
85
|
-
def _update_status(self, message: str):
|
86
|
-
"""Update status output."""
|
87
|
-
if self.enable_progress_output:
|
88
|
-
print(f"STATUS:{message}", flush=True)
|
89
|
-
else:
|
90
|
-
print(f"Status: {message}")
|
91
|
-
|
92
|
-
def _run_command(self, command: str) -> Tuple[int, str, str]:
|
93
|
-
"""
|
94
|
-
Run a shell command and return the exit code, stdout, and stderr.
|
95
|
-
|
96
|
-
Args:
|
97
|
-
command (str): Command to run.
|
98
|
-
|
99
|
-
Returns:
|
100
|
-
Tuple[int, str, str]: Exit code, stdout, and stderr.
|
101
|
-
"""
|
102
|
-
# Use a different approach on Windows to avoid shell issues
|
103
|
-
if sys.platform == 'win32':
|
104
|
-
try:
|
105
|
-
# Split the command into parts for better Windows compatibility
|
106
|
-
if isinstance(command, str):
|
107
|
-
command_parts = command.split()
|
108
|
-
else:
|
109
|
-
command_parts = command
|
110
|
-
|
111
|
-
# Run the command with shell=False for better Windows compatibility
|
112
|
-
process = subprocess.Popen(
|
113
|
-
command_parts,
|
114
|
-
shell=False,
|
115
|
-
stdout=subprocess.PIPE,
|
116
|
-
stderr=subprocess.PIPE,
|
117
|
-
text=True
|
118
|
-
)
|
119
|
-
stdout, stderr = process.communicate()
|
120
|
-
return process.returncode, stdout, stderr
|
121
|
-
except Exception as e:
|
122
|
-
# If there's an error with the non-shell approach, fall back to shell=True
|
123
|
-
self._update_status(f"Warning: Non-shell command execution failed, falling back to shell: {e}")
|
124
|
-
process = subprocess.Popen(
|
125
|
-
command,
|
126
|
-
shell=True,
|
127
|
-
stdout=subprocess.PIPE,
|
128
|
-
stderr=subprocess.PIPE,
|
129
|
-
text=True
|
130
|
-
)
|
131
|
-
stdout, stderr = process.communicate()
|
132
|
-
return process.returncode, stdout, stderr
|
133
|
-
else:
|
134
|
-
# On Unix-like systems, use the original approach
|
135
|
-
process = subprocess.Popen(
|
136
|
-
command,
|
137
|
-
shell=True,
|
138
|
-
stdout=subprocess.PIPE,
|
139
|
-
stderr=subprocess.PIPE,
|
140
|
-
text=True
|
141
|
-
)
|
142
|
-
stdout, stderr = process.communicate()
|
143
|
-
return process.returncode, stdout, stderr
|
144
|
-
|
145
|
-
def download_pokemon_spritesheet(self) -> Path:
|
146
|
-
"""
|
147
|
-
Download the Pokemon spritesheet from Pokemon Showdown.
|
148
|
-
|
149
|
-
Returns:
|
150
|
-
Path to the downloaded spritesheet
|
151
|
-
"""
|
152
|
-
self._update_status("Checking for existing Pokemon spritesheet...")
|
153
|
-
self._update_progress(0.0, "Starting Pokemon spritesheet check...")
|
154
|
-
|
155
|
-
# URL of the Pokemon spritesheet
|
156
|
-
spritesheet_url = "https://play.pokemonshowdown.com/sprites/pokemonicons-sheet.png"
|
157
|
-
spritesheet_path = self.spritesheet_dir / "pokemonicons-sheet.png"
|
158
|
-
|
159
|
-
# Check if the spritesheet already exists
|
160
|
-
if spritesheet_path.exists():
|
161
|
-
self._update_status(f"Using existing spritesheet from {spritesheet_path}")
|
162
|
-
self._update_progress(1.0, "Using existing Pokemon spritesheet")
|
163
|
-
|
164
|
-
# Verify the existing image
|
165
|
-
try:
|
166
|
-
img = Image.open(spritesheet_path)
|
167
|
-
self._update_status(f"Spritesheet dimensions: {img.width}x{img.height}")
|
168
|
-
except Exception as e:
|
169
|
-
self._update_status(f"Warning: Could not verify existing spritesheet image: {e}")
|
170
|
-
self._update_status("Will download a fresh copy...")
|
171
|
-
# If verification fails, we'll download a fresh copy
|
172
|
-
else:
|
173
|
-
return spritesheet_path
|
174
|
-
|
175
|
-
# Download the spritesheet if it doesn't exist or verification failed
|
176
|
-
self._update_status("Downloading Pokemon spritesheet...")
|
177
|
-
try:
|
178
|
-
response = requests.get(spritesheet_url, stream=True)
|
179
|
-
if response.status_code != 200:
|
180
|
-
raise Exception(f"Failed to download spritesheet, status code: {response.status_code}")
|
181
|
-
|
182
|
-
with open(spritesheet_path, 'wb') as f:
|
183
|
-
for chunk in response.iter_content(chunk_size=8192):
|
184
|
-
f.write(chunk)
|
185
|
-
|
186
|
-
self._update_status(f"Spritesheet downloaded successfully to {spritesheet_path}")
|
187
|
-
self._update_progress(1.0, "Pokemon spritesheet download complete")
|
188
|
-
|
189
|
-
# Verify the downloaded image
|
190
|
-
try:
|
191
|
-
img = Image.open(spritesheet_path)
|
192
|
-
self._update_status(f"Spritesheet dimensions: {img.width}x{img.height}")
|
193
|
-
except Exception as e:
|
194
|
-
self._update_status(f"Warning: Could not verify spritesheet image: {e}")
|
195
|
-
|
196
|
-
return spritesheet_path
|
197
|
-
except Exception as e:
|
198
|
-
self._update_status(f"Error downloading Pokemon spritesheet: {str(e)}")
|
199
|
-
raise
|
200
|
-
|
201
|
-
def download_item_spritesheet(self) -> Path:
|
202
|
-
"""
|
203
|
-
Download the item spritesheet from Pokemon Showdown.
|
204
|
-
|
205
|
-
Returns:
|
206
|
-
Path to the downloaded spritesheet
|
207
|
-
"""
|
208
|
-
self._update_status("Checking for existing item spritesheet...")
|
209
|
-
self._update_progress(0.0, "Starting item spritesheet check...")
|
210
|
-
|
211
|
-
# URL of the item spritesheet
|
212
|
-
spritesheet_url = "https://play.pokemonshowdown.com/sprites/itemicons-sheet.png"
|
213
|
-
spritesheet_path = self.spritesheet_dir / "itemicons-sheet.png"
|
214
|
-
|
215
|
-
# Check if the spritesheet already exists
|
216
|
-
if spritesheet_path.exists():
|
217
|
-
self._update_status(f"Using existing spritesheet from {spritesheet_path}")
|
218
|
-
self._update_progress(1.0, "Using existing item spritesheet")
|
219
|
-
|
220
|
-
# Verify the existing image
|
221
|
-
try:
|
222
|
-
img = Image.open(spritesheet_path)
|
223
|
-
self._update_status(f"Spritesheet dimensions: {img.width}x{img.height}")
|
224
|
-
except Exception as e:
|
225
|
-
self._update_status(f"Warning: Could not verify existing spritesheet image: {e}")
|
226
|
-
self._update_status("Will download a fresh copy...")
|
227
|
-
# If verification fails, we'll download a fresh copy
|
228
|
-
else:
|
229
|
-
return spritesheet_path
|
230
|
-
|
231
|
-
# Download the spritesheet if it doesn't exist or verification failed
|
232
|
-
self._update_status("Downloading item spritesheet...")
|
233
|
-
try:
|
234
|
-
response = requests.get(spritesheet_url, stream=True)
|
235
|
-
if response.status_code != 200:
|
236
|
-
raise Exception(f"Failed to download spritesheet, status code: {response.status_code}")
|
237
|
-
|
238
|
-
with open(spritesheet_path, 'wb') as f:
|
239
|
-
for chunk in response.iter_content(chunk_size=8192):
|
240
|
-
f.write(chunk)
|
241
|
-
|
242
|
-
self._update_status(f"Spritesheet downloaded successfully to {spritesheet_path}")
|
243
|
-
self._update_progress(1.0, "Item spritesheet download complete")
|
244
|
-
|
245
|
-
# Verify the downloaded image
|
246
|
-
try:
|
247
|
-
img = Image.open(spritesheet_path)
|
248
|
-
self._update_status(f"Spritesheet dimensions: {img.width}x{img.height}")
|
249
|
-
except Exception as e:
|
250
|
-
self._update_status(f"Warning: Could not verify spritesheet image: {e}")
|
251
|
-
|
252
|
-
return spritesheet_path
|
253
|
-
except Exception as e:
|
254
|
-
self._update_status(f"Error downloading item spritesheet: {str(e)}")
|
255
|
-
raise
|
256
|
-
|
257
|
-
def parse_pokemon_sprite_coordinates(self) -> Dict[str, Dict[str, int]]:
|
258
|
-
"""
|
259
|
-
Parse Pokemon sprite coordinates from the gallery test page.
|
260
|
-
|
261
|
-
Returns:
|
262
|
-
Dictionary mapping Pokemon names to their sprite coordinates
|
263
|
-
"""
|
264
|
-
self._update_status("Checking for existing Pokemon sprite coordinates...")
|
265
|
-
self._update_progress(0.0, "Starting coordinate check...")
|
266
|
-
|
267
|
-
# Check if the coordinates JSON already exists
|
268
|
-
if self.pokemon_coords_file.exists():
|
269
|
-
self._update_status(f"Using existing coordinates from {self.pokemon_coords_file}")
|
270
|
-
try:
|
271
|
-
with open(self.pokemon_coords_file, 'r') as f:
|
272
|
-
pokemon_coords = json.load(f)
|
273
|
-
|
274
|
-
self._update_status(f"Loaded coordinates for {len(pokemon_coords)} Pokemon")
|
275
|
-
self._update_progress(1.0, "Using existing coordinates")
|
276
|
-
return pokemon_coords
|
277
|
-
except Exception as e:
|
278
|
-
self._update_status(f"Error loading existing coordinates: {str(e)}")
|
279
|
-
self._update_status("Will parse fresh coordinates...")
|
280
|
-
# If loading fails, we'll parse fresh coordinates
|
281
|
-
|
282
|
-
# URL of the gallery test page
|
283
|
-
gallery_url = "https://play.pokemonshowdown.com/sprites/gallery-test.html"
|
284
|
-
|
285
|
-
# Create a dictionary to store the Pokemon sprite coordinates
|
286
|
-
pokemon_coords = {}
|
287
|
-
|
288
|
-
# Add MissingNo. to the coordinates dictionary
|
289
|
-
pokemon_coords["MissingNo."] = {
|
290
|
-
"x": 0,
|
291
|
-
"y": 0
|
292
|
-
}
|
293
|
-
|
294
|
-
# Set up Chrome options for headless browsing
|
295
|
-
self._update_status("Setting up headless browser...")
|
296
|
-
chrome_options = Options()
|
297
|
-
chrome_options.add_argument("--headless")
|
298
|
-
chrome_options.add_argument("--no-sandbox")
|
299
|
-
|
300
|
-
try:
|
301
|
-
# Initialize the Chrome driver
|
302
|
-
driver = webdriver.Chrome(options=chrome_options)
|
303
|
-
|
304
|
-
# Navigate to the gallery page
|
305
|
-
self._update_status(f"Navigating to {gallery_url}...")
|
306
|
-
driver.get(gallery_url)
|
307
|
-
|
308
|
-
# Wait for the page to load (wait for the table to be rendered)
|
309
|
-
self._update_status("Waiting for page to load...")
|
310
|
-
WebDriverWait(driver, 10).until(
|
311
|
-
EC.presence_of_element_located((By.TAG_NAME, "table"))
|
312
|
-
)
|
313
|
-
|
314
|
-
# Give the JavaScript some time to fully render the page
|
315
|
-
time.sleep(2)
|
316
|
-
|
317
|
-
# Find the navigation buttons to determine how many pages there are
|
318
|
-
nav_buttons = driver.find_elements(By.CSS_SELECTOR, "ul li button")
|
319
|
-
total_pages = len(nav_buttons)
|
320
|
-
|
321
|
-
self._update_status(f"Found {total_pages} pages of Pokemon data")
|
322
|
-
|
323
|
-
# Process each page
|
324
|
-
total_pokemon = 0
|
325
|
-
for page in range(total_pages):
|
326
|
-
self._update_status(f"Processing page {page+1}/{total_pages}...")
|
327
|
-
|
328
|
-
# If not on the first page, click the navigation button to go to the page
|
329
|
-
if page > 0:
|
330
|
-
# Find the navigation buttons again (they might have been refreshed)
|
331
|
-
nav_buttons = driver.find_elements(By.CSS_SELECTOR, "ul li button")
|
332
|
-
nav_buttons[page].click()
|
333
|
-
|
334
|
-
# Wait for the page to load
|
335
|
-
time.sleep(1)
|
336
|
-
|
337
|
-
# Find all table rows (skip the header row)
|
338
|
-
rows = driver.find_elements(By.CSS_SELECTOR, "table tr:not(:first-child)")
|
339
|
-
|
340
|
-
page_rows = len(rows)
|
341
|
-
self._update_status(f"Found {page_rows} Pokemon entries on page {page+1}")
|
342
|
-
total_pokemon += page_rows
|
343
|
-
|
344
|
-
# Process each row to extract the Pokemon name and sprite coordinates
|
345
|
-
for i, row in enumerate(rows):
|
346
|
-
try:
|
347
|
-
# Get the cells in the row
|
348
|
-
cells = row.find_elements(By.TAG_NAME, "td")
|
349
|
-
|
350
|
-
if len(cells) >= 2:
|
351
|
-
# First cell contains the icon with style attribute containing coordinates
|
352
|
-
icon_cell = cells[0]
|
353
|
-
icon_spans = icon_cell.find_elements(By.CLASS_NAME, "picon")
|
354
|
-
|
355
|
-
if icon_spans:
|
356
|
-
# Get the style attribute which contains the coordinates
|
357
|
-
style = icon_spans[0].get_attribute("style")
|
358
|
-
|
359
|
-
# Extract the coordinates using regex
|
360
|
-
# The format is: background: transparent url("...") Xpx Ypx no-repeat;
|
361
|
-
coords_match = re.search(r'(-?\d+)px (-?\d+)px', style)
|
362
|
-
|
363
|
-
if coords_match:
|
364
|
-
x_coord = int(coords_match.group(1))
|
365
|
-
y_coord = int(coords_match.group(2))
|
366
|
-
|
367
|
-
# Second cell contains the Pokemon name
|
368
|
-
pokemon_name = cells[1].text.strip()
|
369
|
-
|
370
|
-
# Store the coordinates
|
371
|
-
pokemon_coords[pokemon_name] = {
|
372
|
-
"x": x_coord,
|
373
|
-
"y": y_coord
|
374
|
-
}
|
375
|
-
|
376
|
-
except Exception as e:
|
377
|
-
self._update_status(f"Error processing row {i} on page {page+1}: {str(e)}")
|
378
|
-
|
379
|
-
# Update progress after each page
|
380
|
-
progress = (page + 1) / total_pages
|
381
|
-
self._update_progress(progress, f"Processed page {page+1}/{total_pages}")
|
382
|
-
|
383
|
-
# Close the browser
|
384
|
-
driver.quit()
|
385
|
-
|
386
|
-
# Save the coordinates to a JSON file
|
387
|
-
self._update_status(f"Saving coordinates for {len(pokemon_coords)} Pokemon to {self.pokemon_coords_file}...")
|
388
|
-
with open(self.pokemon_coords_file, 'w') as f:
|
389
|
-
json.dump(pokemon_coords, f, indent=2)
|
390
|
-
|
391
|
-
self._update_status(f"Coordinates saved successfully to {self.pokemon_coords_file}")
|
392
|
-
self._update_progress(1.0, "Coordinate parsing complete")
|
393
|
-
|
394
|
-
return pokemon_coords
|
395
|
-
|
396
|
-
except Exception as e:
|
397
|
-
self._update_status(f"Error parsing Pokemon sprite coordinates: {str(e)}")
|
398
|
-
if 'driver' in locals():
|
399
|
-
driver.quit()
|
400
|
-
raise
|
401
|
-
|
402
|
-
def parse_item_sprite_coordinates(self) -> Dict[str, Dict[str, Any]]:
|
403
|
-
"""
|
404
|
-
Parse item sprite coordinates from the items.js data file.
|
405
|
-
|
406
|
-
Returns:
|
407
|
-
Dictionary mapping item names to their sprite coordinates
|
408
|
-
"""
|
409
|
-
self._update_status("Checking for existing item sprite coordinates...")
|
410
|
-
self._update_progress(0.0, "Starting coordinate check...")
|
411
|
-
|
412
|
-
# Check if the coordinates JSON already exists
|
413
|
-
if self.item_coords_file.exists():
|
414
|
-
self._update_status(f"Using existing coordinates from {self.item_coords_file}")
|
415
|
-
try:
|
416
|
-
with open(self.item_coords_file, 'r') as f:
|
417
|
-
item_coords = json.load(f)
|
418
|
-
|
419
|
-
self._update_status(f"Loaded coordinates for {len(item_coords)} items")
|
420
|
-
self._update_progress(1.0, "Using existing coordinates")
|
421
|
-
return item_coords
|
422
|
-
except Exception as e:
|
423
|
-
self._update_status(f"Error loading existing coordinates: {str(e)}")
|
424
|
-
self._update_status("Will parse fresh coordinates...")
|
425
|
-
# If loading fails, we'll parse fresh coordinates
|
426
|
-
|
427
|
-
# Create a dictionary to store the item sprite coordinates
|
428
|
-
item_coords = {}
|
429
|
-
|
430
|
-
try:
|
431
|
-
# Download the items.js file to get item data
|
432
|
-
self._update_status("Downloading item data from Pokemon Showdown...")
|
433
|
-
items_url = "https://play.pokemonshowdown.com/data/items.js"
|
434
|
-
response = requests.get(items_url)
|
435
|
-
|
436
|
-
if response.status_code != 200:
|
437
|
-
raise Exception(f"Failed to download items data, status code: {response.status_code}")
|
438
|
-
|
439
|
-
self._update_progress(0.3, "Item data downloaded, parsing...")
|
440
|
-
|
441
|
-
# Parse the JavaScript content to extract item information
|
442
|
-
content = response.text
|
443
|
-
|
444
|
-
# Extract item entries with names and sprite numbers
|
445
|
-
# Pattern matches: itemid:{...name:"Item Name"...spritenum:123...}
|
446
|
-
item_pattern = r'(\w+):\s*{[^}]*name:\s*"([^"]+)"[^}]*spritenum:\s*(\d+)[^}]*}'
|
447
|
-
item_matches = re.findall(item_pattern, content)
|
448
|
-
|
449
|
-
self._update_status(f"Found {len(item_matches)} items in data file")
|
450
|
-
self._update_progress(0.6, "Calculating sprite coordinates...")
|
451
|
-
|
452
|
-
# Calculate spritesheet grid layout
|
453
|
-
# The spritesheet is 384x1152 with 24x24 sprites
|
454
|
-
sprite_width = 24
|
455
|
-
sprite_height = 24
|
456
|
-
cols = 384 // sprite_width # 16 columns
|
457
|
-
|
458
|
-
# Process each item to calculate coordinates
|
459
|
-
for i, (item_id, item_name, spritenum_str) in enumerate(item_matches):
|
460
|
-
try:
|
461
|
-
spritenum = int(spritenum_str)
|
462
|
-
|
463
|
-
# Calculate grid position from sprite number
|
464
|
-
row = spritenum // cols
|
465
|
-
col = spritenum % cols
|
466
|
-
|
467
|
-
# Calculate pixel coordinates (top-left corner)
|
468
|
-
x = col * sprite_width
|
469
|
-
y = row * sprite_height
|
470
|
-
|
471
|
-
# Store the coordinates
|
472
|
-
item_coords[item_name] = {
|
473
|
-
"x": x,
|
474
|
-
"y": y,
|
475
|
-
"spritenum": spritenum,
|
476
|
-
"item_id": item_id
|
477
|
-
}
|
478
|
-
|
479
|
-
# Update progress periodically
|
480
|
-
if i % 50 == 0 or i == len(item_matches) - 1:
|
481
|
-
progress = 0.6 + (0.3 * (i + 1) / len(item_matches))
|
482
|
-
self._update_progress(progress, f"Processed {i+1}/{len(item_matches)} items")
|
483
|
-
|
484
|
-
except Exception as e:
|
485
|
-
self._update_status(f"Error processing item {item_name}: {str(e)}")
|
486
|
-
|
487
|
-
# Save the coordinates to a JSON file
|
488
|
-
self._update_status(f"Saving coordinates for {len(item_coords)} items to {self.item_coords_file}...")
|
489
|
-
with open(self.item_coords_file, 'w') as f:
|
490
|
-
json.dump(item_coords, f, indent=2)
|
491
|
-
|
492
|
-
self._update_status(f"Coordinates saved successfully to {self.item_coords_file}")
|
493
|
-
self._update_progress(1.0, "Coordinate parsing complete")
|
494
|
-
|
495
|
-
return item_coords
|
496
|
-
|
497
|
-
except Exception as e:
|
498
|
-
self._update_status(f"Error parsing item sprite coordinates: {str(e)}")
|
499
|
-
raise
|
500
|
-
|
501
|
-
def _extract_single_pokemon_sprite(self, args: Tuple[str, Dict[str, int], Image.Image, Path, int, int]) -> Tuple[str, Dict[str, Any]]:
|
502
|
-
"""
|
503
|
-
Extract a single Pokemon sprite from the spritesheet.
|
504
|
-
|
505
|
-
Args:
|
506
|
-
args: Tuple containing (pokemon_name, coords, spritesheet, output_dir, sprite_width, sprite_height)
|
507
|
-
|
508
|
-
Returns:
|
509
|
-
Tuple of (pokemon_name, metadata_dict)
|
510
|
-
"""
|
511
|
-
pokemon_name, coords, spritesheet, output_dir, sprite_width, sprite_height = args
|
512
|
-
|
513
|
-
try:
|
514
|
-
# Get the coordinates
|
515
|
-
x = abs(coords["x"])
|
516
|
-
y = abs(coords["y"])
|
517
|
-
|
518
|
-
# Extract the sprite from the spritesheet
|
519
|
-
sprite = spritesheet.crop((x, y, x + sprite_width, y + sprite_height))
|
520
|
-
|
521
|
-
# Create a safe filename (replace spaces and special characters)
|
522
|
-
safe_name = re.sub(r'[^\w\-_]', '_', pokemon_name)
|
523
|
-
safe_name = safe_name.replace(' ', '').replace('.','').replace('-','').replace('_','').lower()
|
524
|
-
sprite_path = output_dir / f"{safe_name}.png"
|
525
|
-
|
526
|
-
# Save the sprite with transparency
|
527
|
-
sprite.save(sprite_path, 'PNG')
|
528
|
-
|
529
|
-
# Create metadata
|
530
|
-
metadata = {
|
531
|
-
"filename": f"{safe_name}.png",
|
532
|
-
"path": str(sprite_path),
|
533
|
-
"coordinates": {
|
534
|
-
"x": x,
|
535
|
-
"y": y,
|
536
|
-
"width": sprite_width,
|
537
|
-
"height": sprite_height
|
538
|
-
},
|
539
|
-
"extracted_at": time.strftime("%Y-%m-%d %H:%M:%S")
|
540
|
-
}
|
541
|
-
|
542
|
-
return pokemon_name, metadata
|
543
|
-
|
544
|
-
except Exception as e:
|
545
|
-
self._update_status(f"Error extracting sprite for {pokemon_name}: {str(e)}")
|
546
|
-
return pokemon_name, None
|
547
|
-
|
548
|
-
def extract_pokemon_sprites(self, spritesheet_path: Path, pokemon_coords: Dict[str, Dict[str, int]]) -> Path:
|
549
|
-
"""
|
550
|
-
Extract individual Pokemon sprites from the spritesheet using parallel processing.
|
551
|
-
|
552
|
-
Args:
|
553
|
-
spritesheet_path: Path to the spritesheet image
|
554
|
-
pokemon_coords: Dictionary mapping Pokemon names to their sprite coordinates
|
555
|
-
|
556
|
-
Returns:
|
557
|
-
Path to the directory containing the extracted sprites
|
558
|
-
"""
|
559
|
-
self._update_status("Checking for existing Pokemon sprite metadata...")
|
560
|
-
self._update_progress(0.0, "Starting sprite extraction check...")
|
561
|
-
|
562
|
-
# Check if the metadata JSON already exists
|
563
|
-
if self.pokemon_metadata_file.exists():
|
564
|
-
self._update_status(f"Using existing sprite metadata from {self.pokemon_metadata_file}")
|
565
|
-
try:
|
566
|
-
with open(self.pokemon_metadata_file, 'r') as f:
|
567
|
-
metadata = json.load(f)
|
568
|
-
|
569
|
-
# Verify that the sprites directory exists and has files
|
570
|
-
if self.pokemon_sprites_dir.exists() and len(list(self.pokemon_sprites_dir.glob("*.png"))) > 0:
|
571
|
-
self._update_status(f"Found existing sprites in {self.pokemon_sprites_dir}")
|
572
|
-
self._update_progress(1.0, "Using existing sprites")
|
573
|
-
return self.pokemon_sprites_dir
|
574
|
-
else:
|
575
|
-
self._update_status("Sprite directory empty or missing, will extract sprites...")
|
576
|
-
except Exception as e:
|
577
|
-
self._update_status(f"Error loading existing metadata: {str(e)}")
|
578
|
-
self._update_status("Will extract sprites...")
|
579
|
-
# If loading fails, we'll extract sprites
|
580
|
-
|
581
|
-
self._update_status("Extracting Pokemon sprites from spritesheet using parallel processing...")
|
582
|
-
self._update_progress(0.1, "Starting parallel sprite extraction...")
|
583
|
-
|
584
|
-
# Create a metadata file to store information about the sprites
|
585
|
-
metadata = {}
|
586
|
-
|
587
|
-
try:
|
588
|
-
# Load the spritesheet image
|
589
|
-
spritesheet = Image.open(spritesheet_path)
|
590
|
-
self._update_status(f"Loaded spritesheet with dimensions: {spritesheet.width}x{spritesheet.height}")
|
591
|
-
|
592
|
-
# Convert to RGBA to preserve transparency
|
593
|
-
if spritesheet.mode != 'RGBA':
|
594
|
-
spritesheet = spritesheet.convert('RGBA')
|
595
|
-
|
596
|
-
# Define the size of each sprite (40x30 pixels)
|
597
|
-
sprite_width = 40
|
598
|
-
sprite_height = 30
|
599
|
-
|
600
|
-
# Prepare arguments for parallel processing
|
601
|
-
total_pokemon = len(pokemon_coords)
|
602
|
-
self._update_status(f"Starting parallel extraction of {total_pokemon} Pokemon sprites with {self.max_workers} workers...")
|
603
|
-
|
604
|
-
# Create arguments for each sprite extraction
|
605
|
-
extraction_args = []
|
606
|
-
for pokemon_name, coords in pokemon_coords.items():
|
607
|
-
args = (pokemon_name, coords, spritesheet, self.pokemon_sprites_dir, sprite_width, sprite_height)
|
608
|
-
extraction_args.append(args)
|
609
|
-
|
610
|
-
# Thread-safe counter for progress tracking
|
611
|
-
completed_count = 0
|
612
|
-
lock = threading.Lock()
|
613
|
-
|
614
|
-
def update_progress():
|
615
|
-
nonlocal completed_count
|
616
|
-
with lock:
|
617
|
-
completed_count += 1
|
618
|
-
progress = 0.1 + (0.8 * completed_count / total_pokemon)
|
619
|
-
if completed_count % 10 == 0 or completed_count == total_pokemon:
|
620
|
-
self._update_progress(progress, f"Extracted {completed_count}/{total_pokemon} sprites")
|
621
|
-
|
622
|
-
# Process sprites in parallel
|
623
|
-
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
|
624
|
-
# Submit all extraction tasks
|
625
|
-
future_to_pokemon = {
|
626
|
-
executor.submit(self._extract_single_pokemon_sprite, args): args[0]
|
627
|
-
for args in extraction_args
|
628
|
-
}
|
629
|
-
|
630
|
-
# Collect results as they complete
|
631
|
-
for future in concurrent.futures.as_completed(future_to_pokemon):
|
632
|
-
pokemon_name, sprite_metadata = future.result()
|
633
|
-
if sprite_metadata:
|
634
|
-
metadata[pokemon_name] = sprite_metadata
|
635
|
-
update_progress()
|
636
|
-
|
637
|
-
# Save the metadata
|
638
|
-
with open(self.pokemon_metadata_file, 'w') as f:
|
639
|
-
json.dump(metadata, f, indent=2)
|
640
|
-
|
641
|
-
self._update_status(f"Extracted {len(metadata)} Pokemon sprites to {self.pokemon_sprites_dir}")
|
642
|
-
self._update_status(f"Metadata saved to {self.pokemon_metadata_file}")
|
643
|
-
self._update_progress(1.0, "Parallel sprite extraction complete")
|
644
|
-
|
645
|
-
return self.pokemon_sprites_dir
|
646
|
-
|
647
|
-
except Exception as e:
|
648
|
-
self._update_status(f"Error extracting Pokemon sprites: {str(e)}")
|
649
|
-
raise
|
650
|
-
|
651
|
-
def _extract_single_item_sprite(self, args: Tuple[str, Dict[str, Any], Image.Image, Path, int, int]) -> Tuple[str, Dict[str, Any]]:
|
652
|
-
"""
|
653
|
-
Extract a single item sprite from the spritesheet.
|
654
|
-
|
655
|
-
Args:
|
656
|
-
args: Tuple containing (item_name, coords, spritesheet, output_dir, sprite_width, sprite_height)
|
657
|
-
|
658
|
-
Returns:
|
659
|
-
Tuple of (item_name, metadata_dict)
|
660
|
-
"""
|
661
|
-
item_name, coords, spritesheet, output_dir, sprite_width, sprite_height = args
|
662
|
-
|
663
|
-
try:
|
664
|
-
# Get the coordinates
|
665
|
-
x = abs(coords["x"])
|
666
|
-
y = abs(coords["y"])
|
667
|
-
|
668
|
-
# Extract the sprite from the spritesheet
|
669
|
-
sprite = spritesheet.crop((x, y, x + sprite_width, y + sprite_height))
|
670
|
-
|
671
|
-
# Create a safe filename (replace spaces and special characters)
|
672
|
-
safe_name = re.sub(r'[^\w\-_]', '_', item_name)
|
673
|
-
safe_name = safe_name.replace(' ', '').replace('.','').replace('-','').replace('_','').lower()
|
674
|
-
sprite_path = output_dir / f"{safe_name}.png"
|
675
|
-
|
676
|
-
# Save the sprite with transparency
|
677
|
-
sprite.save(sprite_path, 'PNG')
|
678
|
-
|
679
|
-
# Create metadata
|
680
|
-
metadata = {
|
681
|
-
"filename": f"{safe_name}.png",
|
682
|
-
"path": str(sprite_path),
|
683
|
-
"coordinates": {
|
684
|
-
"x": x,
|
685
|
-
"y": y,
|
686
|
-
"width": sprite_width,
|
687
|
-
"height": sprite_height
|
688
|
-
},
|
689
|
-
"extracted_at": time.strftime("%Y-%m-%d %H:%M:%S")
|
690
|
-
}
|
691
|
-
|
692
|
-
return item_name, metadata
|
693
|
-
|
694
|
-
except Exception as e:
|
695
|
-
self._update_status(f"Error extracting sprite for {item_name}: {str(e)}")
|
696
|
-
return item_name, None
|
697
|
-
|
698
|
-
def extract_item_sprites(self, spritesheet_path: Path, item_coords: Dict[str, Dict[str, Any]]) -> Path:
|
699
|
-
"""
|
700
|
-
Extract individual item sprites from the spritesheet using parallel processing.
|
701
|
-
|
702
|
-
Args:
|
703
|
-
spritesheet_path: Path to the spritesheet image
|
704
|
-
item_coords: Dictionary mapping item names to their sprite coordinates
|
705
|
-
|
706
|
-
Returns:
|
707
|
-
Path to the directory containing the extracted sprites
|
708
|
-
"""
|
709
|
-
self._update_status("Checking for existing item sprite metadata...")
|
710
|
-
self._update_progress(0.0, "Starting sprite extraction check...")
|
711
|
-
|
712
|
-
# Check if the metadata JSON already exists
|
713
|
-
if self.item_metadata_file.exists():
|
714
|
-
self._update_status(f"Using existing sprite metadata from {self.item_metadata_file}")
|
715
|
-
try:
|
716
|
-
with open(self.item_metadata_file, 'r') as f:
|
717
|
-
metadata = json.load(f)
|
718
|
-
|
719
|
-
# Verify that the sprites directory exists and has files
|
720
|
-
if self.item_sprites_dir.exists() and len(list(self.item_sprites_dir.glob("*.png"))) > 0:
|
721
|
-
self._update_status(f"Found existing sprites in {self.item_sprites_dir}")
|
722
|
-
self._update_progress(1.0, "Using existing sprites")
|
723
|
-
return self.item_sprites_dir
|
724
|
-
else:
|
725
|
-
self._update_status("Sprite directory empty or missing, will extract sprites...")
|
726
|
-
except Exception as e:
|
727
|
-
self._update_status(f"Error loading existing metadata: {str(e)}")
|
728
|
-
self._update_status("Will extract sprites...")
|
729
|
-
# If loading fails, we'll extract sprites
|
730
|
-
|
731
|
-
self._update_status("Extracting item sprites from spritesheet using parallel processing...")
|
732
|
-
self._update_progress(0.1, "Starting parallel sprite extraction...")
|
733
|
-
|
734
|
-
# Create a metadata file to store information about the sprites
|
735
|
-
metadata = {}
|
736
|
-
|
737
|
-
try:
|
738
|
-
# Load the spritesheet image
|
739
|
-
spritesheet = Image.open(spritesheet_path)
|
740
|
-
self._update_status(f"Loaded spritesheet with dimensions: {spritesheet.width}x{spritesheet.height}")
|
741
|
-
|
742
|
-
# Convert to RGBA to preserve transparency
|
743
|
-
if spritesheet.mode != 'RGBA':
|
744
|
-
spritesheet = spritesheet.convert('RGBA')
|
745
|
-
|
746
|
-
# Define the size of each sprite (24x24 pixels for items)
|
747
|
-
sprite_width = 24
|
748
|
-
sprite_height = 24
|
749
|
-
|
750
|
-
# Prepare arguments for parallel processing
|
751
|
-
total_items = len(item_coords)
|
752
|
-
self._update_status(f"Starting parallel extraction of {total_items} item sprites with {self.max_workers} workers...")
|
753
|
-
|
754
|
-
# Create arguments for each sprite extraction
|
755
|
-
extraction_args = []
|
756
|
-
for item_name, coords in item_coords.items():
|
757
|
-
args = (item_name, coords, spritesheet, self.item_sprites_dir, sprite_width, sprite_height)
|
758
|
-
extraction_args.append(args)
|
759
|
-
|
760
|
-
# Thread-safe counter for progress tracking
|
761
|
-
completed_count = 0
|
762
|
-
lock = threading.Lock()
|
763
|
-
|
764
|
-
def update_progress():
|
765
|
-
nonlocal completed_count
|
766
|
-
with lock:
|
767
|
-
completed_count += 1
|
768
|
-
progress = 0.1 + (0.8 * completed_count / total_items)
|
769
|
-
if completed_count % 10 == 0 or completed_count == total_items:
|
770
|
-
self._update_progress(progress, f"Extracted {completed_count}/{total_items} sprites")
|
771
|
-
|
772
|
-
# Process sprites in parallel
|
773
|
-
with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
|
774
|
-
# Submit all extraction tasks
|
775
|
-
future_to_item = {
|
776
|
-
executor.submit(self._extract_single_item_sprite, args): args[0]
|
777
|
-
for args in extraction_args
|
778
|
-
}
|
779
|
-
|
780
|
-
# Collect results as they complete
|
781
|
-
for future in concurrent.futures.as_completed(future_to_item):
|
782
|
-
item_name, sprite_metadata = future.result()
|
783
|
-
if sprite_metadata:
|
784
|
-
metadata[item_name] = sprite_metadata
|
785
|
-
update_progress()
|
786
|
-
|
787
|
-
# Save the metadata
|
788
|
-
with open(self.item_metadata_file, 'w') as f:
|
789
|
-
json.dump(metadata, f, indent=2)
|
790
|
-
|
791
|
-
self._update_status(f"Extracted {len(metadata)} item sprites to {self.item_sprites_dir}")
|
792
|
-
self._update_status(f"Metadata saved to {self.item_metadata_file}")
|
793
|
-
self._update_progress(1.0, "Parallel sprite extraction complete")
|
794
|
-
|
795
|
-
return self.item_sprites_dir
|
796
|
-
|
797
|
-
except Exception as e:
|
798
|
-
self._update_status(f"Error extracting item sprites: {str(e)}")
|
799
|
-
raise
|
800
|
-
|
801
|
-
def download_all_sprites(self) -> Dict[str, Path]:
|
802
|
-
"""
|
803
|
-
Download and extract all sprites (Pokemon and items).
|
804
|
-
|
805
|
-
Returns:
|
806
|
-
Dictionary with paths to the extracted sprite directories
|
807
|
-
"""
|
808
|
-
self._update_status("Starting sprite download process...")
|
809
|
-
self._update_progress(0.0, "Starting sprite download...")
|
810
|
-
|
811
|
-
try:
|
812
|
-
# Download Pokemon spritesheet
|
813
|
-
pokemon_spritesheet = self.download_pokemon_spritesheet()
|
814
|
-
self._update_status(f"Pokemon spritesheet ready at: {pokemon_spritesheet}")
|
815
|
-
self._update_progress(0.2, "Pokemon spritesheet downloaded")
|
816
|
-
|
817
|
-
# Download item spritesheet
|
818
|
-
item_spritesheet = self.download_item_spritesheet()
|
819
|
-
self._update_status(f"Item spritesheet ready at: {item_spritesheet}")
|
820
|
-
self._update_progress(0.4, "Item spritesheet downloaded")
|
821
|
-
|
822
|
-
# Parse Pokemon sprite coordinates
|
823
|
-
pokemon_coords = self.parse_pokemon_sprite_coordinates()
|
824
|
-
self._update_status(f"Pokemon sprite coordinates parsed")
|
825
|
-
self._update_status(f"Found coordinates for {len(pokemon_coords)} Pokemon")
|
826
|
-
self._update_progress(0.6, "Pokemon coordinates parsed")
|
827
|
-
|
828
|
-
# Parse item sprite coordinates
|
829
|
-
item_coords = self.parse_item_sprite_coordinates()
|
830
|
-
self._update_status(f"Item sprite coordinates parsed")
|
831
|
-
self._update_status(f"Found coordinates for {len(item_coords)} items")
|
832
|
-
self._update_progress(0.7, "Item coordinates parsed")
|
833
|
-
|
834
|
-
# Extract Pokemon sprites
|
835
|
-
pokemon_sprites_dir = self.extract_pokemon_sprites(pokemon_spritesheet, pokemon_coords)
|
836
|
-
self._update_status(f"Pokemon sprites extracted to: {pokemon_sprites_dir}")
|
837
|
-
self._update_progress(0.85, "Pokemon sprites extracted")
|
838
|
-
|
839
|
-
# Extract item sprites
|
840
|
-
item_sprites_dir = self.extract_item_sprites(item_spritesheet, item_coords)
|
841
|
-
self._update_status(f"Item sprites extracted to: {item_sprites_dir}")
|
842
|
-
self._update_progress(1.0, "All sprites downloaded and extracted")
|
843
|
-
|
844
|
-
return {
|
845
|
-
"pokemon": pokemon_sprites_dir,
|
846
|
-
"items": item_sprites_dir
|
847
|
-
}
|
848
|
-
|
849
|
-
except Exception as e:
|
850
|
-
self._update_status(f"Error in sprite download process: {str(e)}")
|
851
|
-
raise
|
852
|
-
|
853
|
-
def get_pokemon_sprite_path(self, pokemon_name: str) -> Optional[Path]:
|
854
|
-
"""
|
855
|
-
Get the path to a Pokemon sprite.
|
856
|
-
|
857
|
-
Args:
|
858
|
-
pokemon_name: Name of the Pokemon
|
859
|
-
|
860
|
-
Returns:
|
861
|
-
Path to the sprite file, or None if not found
|
862
|
-
"""
|
863
|
-
# Create safe filename
|
864
|
-
safe_name = re.sub(r'[^\w\-_]', '_', pokemon_name)
|
865
|
-
safe_name = safe_name.replace(' ', '').replace('.','').replace('-','').replace('_','').lower()
|
866
|
-
sprite_path = self.pokemon_sprites_dir / f"{safe_name}.png"
|
867
|
-
|
868
|
-
if sprite_path.exists():
|
869
|
-
return sprite_path
|
870
|
-
return None
|
871
|
-
|
872
|
-
def get_item_sprite_path(self, item_name: str) -> Optional[Path]:
|
873
|
-
"""
|
874
|
-
Get the path to an item sprite.
|
875
|
-
|
876
|
-
Args:
|
877
|
-
item_name: Name of the item
|
878
|
-
|
879
|
-
Returns:
|
880
|
-
Path to the sprite file, or None if not found
|
881
|
-
"""
|
882
|
-
# Create safe filename
|
883
|
-
safe_name = re.sub(r'[^\w\-_]', '_', item_name)
|
884
|
-
safe_name = safe_name.replace(' ', '').replace('.','').replace('-','').replace('_','').lower()
|
885
|
-
sprite_path = self.item_sprites_dir / f"{safe_name}.png"
|
886
|
-
|
887
|
-
if sprite_path.exists():
|
888
|
-
return sprite_path
|
889
|
-
return None
|
890
|
-
|
891
|
-
def get_sprite_metadata(self, sprite_type: str = "pokemon") -> Dict[str, Any]:
|
892
|
-
"""
|
893
|
-
Get sprite metadata.
|
894
|
-
|
895
|
-
Args:
|
896
|
-
sprite_type: Type of sprites ("pokemon" or "items")
|
897
|
-
|
898
|
-
Returns:
|
899
|
-
Dictionary containing sprite metadata
|
900
|
-
"""
|
901
|
-
if sprite_type == "pokemon":
|
902
|
-
metadata_file = self.pokemon_metadata_file
|
903
|
-
elif sprite_type == "items":
|
904
|
-
metadata_file = self.item_metadata_file
|
905
|
-
else:
|
906
|
-
raise ValueError("sprite_type must be 'pokemon' or 'items'")
|
907
|
-
|
908
|
-
if metadata_file.exists():
|
909
|
-
with open(metadata_file, 'r') as f:
|
910
|
-
return json.load(f)
|
911
|
-
return {}
|
912
|
-
|
913
|
-
def list_available_sprites(self, sprite_type: str = "pokemon") -> List[str]:
|
914
|
-
"""
|
915
|
-
List all available sprites of a given type.
|
916
|
-
|
917
|
-
Args:
|
918
|
-
sprite_type: Type of sprites ("pokemon" or "items")
|
919
|
-
|
920
|
-
Returns:
|
921
|
-
List of sprite names
|
922
|
-
"""
|
923
|
-
if sprite_type == "pokemon":
|
924
|
-
sprite_dir = self.pokemon_sprites_dir
|
925
|
-
elif sprite_type == "items":
|
926
|
-
sprite_dir = self.item_sprites_dir
|
927
|
-
else:
|
928
|
-
raise ValueError("sprite_type must be 'pokemon' or 'items'")
|
929
|
-
|
930
|
-
if sprite_dir.exists():
|
931
|
-
return [f.stem for f in sprite_dir.glob("*.png")]
|
932
|
-
return []
|
File without changes
|
File without changes
|
File without changes
|