Update app.py
Browse files
app.py
CHANGED
|
@@ -50,28 +50,6 @@ dinov2_model.eval()
|
|
| 50 |
phash = PHash()
|
| 51 |
gis = ImageSignature()
|
| 52 |
|
| 53 |
-
def log_execution_time(func):
|
| 54 |
-
@functools.wraps(func)
|
| 55 |
-
def wrapper(*args, **kwargs):
|
| 56 |
-
start_time = time.time()
|
| 57 |
-
result = func(*args, **kwargs)
|
| 58 |
-
end_time = time.time()
|
| 59 |
-
logger.info(f"β± {func.__name__} executed in {end_time - start_time:.2f} seconds")
|
| 60 |
-
return result
|
| 61 |
-
return wrapper
|
| 62 |
-
|
| 63 |
-
# Configure logging
|
| 64 |
-
logging.basicConfig(
|
| 65 |
-
level=logging.DEBUG, # Use INFO or ERROR in production
|
| 66 |
-
format="%(asctime)s [%(levelname)s] %(message)s",
|
| 67 |
-
handlers=[
|
| 68 |
-
logging.FileHandler("/app/logs/app.log"),
|
| 69 |
-
logging.StreamHandler()
|
| 70 |
-
]
|
| 71 |
-
)
|
| 72 |
-
|
| 73 |
-
logger = logging.getLogger(__name__)
|
| 74 |
-
|
| 75 |
load_dotenv()
|
| 76 |
# os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY")
|
| 77 |
groq_api_key = os.getenv("GROQ_API_KEY")
|
|
@@ -84,26 +62,33 @@ llm = ChatGroq(
|
|
| 84 |
|
| 85 |
app = Flask(__name__)
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
count = 0
|
| 88 |
|
| 89 |
-
|
| 90 |
-
# BLOCKS_DIR = BASE_DIR / "blocks"
|
| 91 |
-
# STATIC_DIR = BASE_DIR / "static"
|
| 92 |
-
# GEN_PROJECT_DIR = BASE_DIR / "generated_projects"
|
| 93 |
BASE_DIR = Path(os.getenv("APP_BASE_DIR", Path(__file__).resolve().parent)) # fallback to code location
|
| 94 |
-
LOGS_DIR =
|
|
|
|
| 95 |
for d in (BASE_DIR / "blocks", BASE_DIR / "static", BASE_DIR / "generated_projects", LOGS_DIR, BASE_DIR / "outputs"):
|
| 96 |
d.mkdir(parents=True, exist_ok=True)
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
# Global variables to hold the model and index, loaded only once.
|
| 109 |
MODEL = None
|
|
@@ -121,7 +106,30 @@ for d in (
|
|
| 121 |
OUTPUT_DIR,
|
| 122 |
):
|
| 123 |
d.mkdir(parents=True, exist_ok=True)
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
class GameState(TypedDict):
|
| 126 |
project_json: dict
|
| 127 |
description: str
|
|
@@ -1427,15 +1435,11 @@ def similarity_matching(sprites_data: dict, project_folder: str, top_k: int = 1,
|
|
| 1427 |
import json
|
| 1428 |
os.makedirs(project_folder, exist_ok=True)
|
| 1429 |
|
| 1430 |
-
|
| 1431 |
-
|
| 1432 |
-
|
| 1433 |
-
# project_json_path = os.path.join(project_folder, "project.json").resolve()
|
| 1434 |
-
backdrop_base_path = BACKDROP_DIR.resolve()
|
| 1435 |
-
sprite_base_path = SPRITE_DIR.resolve()
|
| 1436 |
-
code_blocks_path = CODE_BLOCKS_DIR.resolve()
|
| 1437 |
-
project_json_path = (Path(project_folder) / "project.json").resolve()
|
| 1438 |
|
|
|
|
| 1439 |
|
| 1440 |
# -------------------------
|
| 1441 |
# Build sprite images list (BytesIO) from sprites_data
|
|
@@ -1944,272 +1948,265 @@ def similarity_matching(sprites_data: dict, project_folder: str, top_k: int = 1,
|
|
| 1944 |
|
| 1945 |
# print("---")
|
| 1946 |
# add at top of file
|
| 1947 |
-
|
| 1948 |
-
|
| 1949 |
-
|
| 1950 |
-
|
| 1951 |
-
|
| 1952 |
-
# # normalize base paths once before the loop
|
| 1953 |
-
# sprite_base_p = Path(sprite_base_path).resolve(strict=False)
|
| 1954 |
-
# backdrop_base_p = Path(backdrop_base_path).resolve(strict=False)
|
| 1955 |
-
# project_folder_p = Path(project_folder)
|
| 1956 |
-
# project_folder_p.mkdir(parents=True, exist_ok=True)
|
| 1957 |
-
|
| 1958 |
-
# copied_sprite_folders = set()
|
| 1959 |
-
# copied_backdrop_folders = set()
|
| 1960 |
-
|
| 1961 |
-
# def display_like_windows_no_lead(p: Path) -> str:
|
| 1962 |
-
# """
|
| 1963 |
-
# For human-readable logs only β convert Path to a string like:
|
| 1964 |
-
# "app\\blocks\\Backdrops\\Castle 2.sb3" (no leading slash).
|
| 1965 |
-
# """
|
| 1966 |
-
# s = p.as_posix() # forward-slash string, safe for Path objects
|
| 1967 |
-
# if s.startswith("/"):
|
| 1968 |
-
# s = s[1:]
|
| 1969 |
-
# return s.replace("/", "\\")
|
| 1970 |
-
|
| 1971 |
-
# def is_subpath(child: Path, parent: Path) -> bool:
|
| 1972 |
-
# """Robust membership test: is child under parent?"""
|
| 1973 |
-
# try:
|
| 1974 |
-
# # use non-strict resolve only if needed, but avoid exceptions
|
| 1975 |
-
# child.relative_to(parent)
|
| 1976 |
-
# return True
|
| 1977 |
-
# except Exception:
|
| 1978 |
-
# return False
|
| 1979 |
-
|
| 1980 |
-
# # Flatten unique matched indices (if not already)
|
| 1981 |
-
# matched_indices = sorted({idx for lst in per_sprite_matched_indices for idx in lst})
|
| 1982 |
-
# print("matched_indices------------------>", matched_indices)
|
| 1983 |
-
|
| 1984 |
-
# for matched_idx in matched_indices:
|
| 1985 |
-
# # defensive check
|
| 1986 |
-
# if not (0 <= matched_idx < len(paths_list)):
|
| 1987 |
-
# print(f" β matched_idx {matched_idx} out of range, skipping")
|
| 1988 |
-
# continue
|
| 1989 |
-
|
| 1990 |
-
# matched_image_path = paths_list[matched_idx]
|
| 1991 |
-
# matched_path_p = Path(matched_image_path).resolve(strict=False) # keep as Path
|
| 1992 |
-
# matched_folder_p = matched_path_p.parent # Path object
|
| 1993 |
-
# matched_filename = matched_path_p.name
|
| 1994 |
-
|
| 1995 |
-
# # Prepare display-only string (do NOT reassign matched_folder_p)
|
| 1996 |
-
# matched_folder_display = display_like_windows_no_lead(matched_folder_p)
|
| 1997 |
|
| 1998 |
-
#
|
| 1999 |
-
|
| 2000 |
-
|
| 2001 |
-
|
| 2002 |
-
|
| 2003 |
|
| 2004 |
-
|
| 2005 |
-
|
| 2006 |
|
| 2007 |
-
|
| 2008 |
-
|
| 2009 |
-
|
| 2010 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2011 |
|
| 2012 |
-
|
| 2013 |
-
|
| 2014 |
-
|
| 2015 |
-
|
| 2016 |
-
|
| 2017 |
-
|
| 2018 |
-
|
| 2019 |
-
|
| 2020 |
-
# print(f" β Successfully read sprite.json from {matched_folder_display}")
|
| 2021 |
-
# except Exception as e:
|
| 2022 |
-
# print(f" β Failed to read sprite.json in {matched_folder_display}: {repr(e)}")
|
| 2023 |
-
# else:
|
| 2024 |
-
# print(f" β No sprite.json in {matched_folder_display}")
|
| 2025 |
|
| 2026 |
-
#
|
| 2027 |
-
|
| 2028 |
-
|
| 2029 |
-
# except Exception as e:
|
| 2030 |
-
# sprite_files = []
|
| 2031 |
-
# print(f" β Failed to list files in {matched_folder_display}: {repr(e)}")
|
| 2032 |
|
| 2033 |
-
|
| 2034 |
-
|
| 2035 |
-
|
| 2036 |
-
|
| 2037 |
-
|
| 2038 |
-
# continue
|
| 2039 |
-
# if p.is_file():
|
| 2040 |
-
# dst = project_folder_p / fname
|
| 2041 |
-
# try:
|
| 2042 |
-
# shutil.copy2(str(p), str(dst))
|
| 2043 |
-
# print(f" β Copied sprite asset: {p} -> {dst}")
|
| 2044 |
-
# except Exception as e:
|
| 2045 |
-
# print(f" β Failed to copy sprite asset {p}: {repr(e)}")
|
| 2046 |
-
# else:
|
| 2047 |
-
# print(f" Skipping {fname} (not a file)")
|
| 2048 |
|
| 2049 |
-
|
| 2050 |
-
|
| 2051 |
-
|
| 2052 |
-
|
| 2053 |
-
# print("backdrop_base_path----------------------->", display_like_windows_no_lead(backdrop_base_p))
|
| 2054 |
-
# print("copied backdrop folder----------------------->", copied_backdrop_folders)
|
| 2055 |
|
| 2056 |
-
|
| 2057 |
-
|
| 2058 |
-
# backdrop_dst = project_folder_p / matched_filename
|
| 2059 |
-
# if backdrop_src.exists() and backdrop_src.is_file():
|
| 2060 |
-
# try:
|
| 2061 |
-
# shutil.copy2(str(backdrop_src), str(backdrop_dst))
|
| 2062 |
-
# print(f" β Copied matched backdrop image: {backdrop_src} -> {backdrop_dst}")
|
| 2063 |
-
# except Exception as e:
|
| 2064 |
-
# print(f" β Failed to copy matched backdrop image {backdrop_src}: {repr(e)}")
|
| 2065 |
-
# else:
|
| 2066 |
-
# print(f" β Matched backdrop source not found: {backdrop_src}")
|
| 2067 |
|
| 2068 |
-
|
| 2069 |
-
|
| 2070 |
-
|
| 2071 |
-
|
| 2072 |
-
|
| 2073 |
-
# print(f" β Failed to list files in {matched_folder_display}: {repr(e)}")
|
| 2074 |
|
| 2075 |
-
|
| 2076 |
-
|
| 2077 |
-
# fname = p.name
|
| 2078 |
-
# if fname in (matched_filename, "project.json"):
|
| 2079 |
-
# print(f" Skipping {fname} (matched image or project.json)")
|
| 2080 |
-
# continue
|
| 2081 |
-
# if p.is_file():
|
| 2082 |
-
# dst = project_folder_p / fname
|
| 2083 |
-
# try:
|
| 2084 |
-
# shutil.copy2(str(p), str(dst))
|
| 2085 |
-
# print(f" β Copied backdrop asset: {p} -> {dst}")
|
| 2086 |
-
# except Exception as e:
|
| 2087 |
-
# print(f" β Failed to copy backdrop asset {p}: {repr(e)}")
|
| 2088 |
-
# else:
|
| 2089 |
-
# print(f" Skipping {fname} (not a file)")
|
| 2090 |
|
| 2091 |
-
|
| 2092 |
-
|
| 2093 |
-
|
| 2094 |
-
|
| 2095 |
-
# with pj.open("r", encoding="utf-8") as f:
|
| 2096 |
-
# bd_json = json.load(f)
|
| 2097 |
-
# stage_count = 0
|
| 2098 |
-
# for tgt in bd_json.get("targets", []):
|
| 2099 |
-
# if tgt.get("isStage"):
|
| 2100 |
-
# backdrop_data.append(tgt)
|
| 2101 |
-
# stage_count += 1
|
| 2102 |
-
# print(f" β Successfully read project.json from {matched_folder_display}, found {stage_count} stage(s)")
|
| 2103 |
-
# except Exception as e:
|
| 2104 |
-
# print(f" β Failed to read project.json in {matched_folder_display}: {repr(e)}")
|
| 2105 |
-
# else:
|
| 2106 |
-
# print(f" β No project.json in {matched_folder_display}")
|
| 2107 |
|
| 2108 |
-
|
| 2109 |
-
|
| 2110 |
-
|
| 2111 |
-
|
| 2112 |
-
matched_folder = str(Path(matched_image_path).parent.resolve())
|
| 2113 |
-
matched_filename = os.path.basename(matched_image_path)
|
| 2114 |
-
|
| 2115 |
-
print(f"Processing matched image: {matched_image_path}")
|
| 2116 |
-
print(f" - Folder: {matched_folder}")
|
| 2117 |
-
print(f" - Sprite path: {sprite_base_path}")
|
| 2118 |
-
print(f" - Backdrop path: {backdrop_base_path}")
|
| 2119 |
-
print(f" - Filename: {matched_filename}")
|
| 2120 |
-
|
| 2121 |
-
# If it's a sprite (under SPRITE_DIR) -> copy sprite assets and read sprite.json
|
| 2122 |
-
# if matched_folder.startswith(sprite_base_path) and matched_folder not in copied_sprite_folders:
|
| 2123 |
-
if is_subpath(matched_folder, sprite_base_path) and matched_folder not in copied_sprite_folders:
|
| 2124 |
-
print(f"Processing SPRITE folder: {matched_folder}")
|
| 2125 |
-
copied_sprite_folders.add(matched_folder)
|
| 2126 |
-
# sprite_json_path = os.path.join(matched_folder, "sprite.json")
|
| 2127 |
-
sprite_json_path = str(Path(matched_folder) / "sprite.json")
|
| 2128 |
-
print("sprite_json_path----------------------->",sprite_json_path)
|
| 2129 |
-
print("copied sprite folder----------------------->",copied_sprite_folders)
|
| 2130 |
-
if os.path.exists(sprite_json_path):
|
| 2131 |
try:
|
| 2132 |
-
with open(
|
| 2133 |
sprite_info = json.load(f)
|
| 2134 |
project_data.append(sprite_info)
|
| 2135 |
-
print(f" β Successfully read sprite.json from {
|
| 2136 |
except Exception as e:
|
| 2137 |
-
print(f" β Failed to read sprite.json in {
|
| 2138 |
else:
|
| 2139 |
-
print(f" β No sprite.json in {
|
| 2140 |
-
|
| 2141 |
-
# copy non-matching files from the sprite folder (except
|
| 2142 |
-
|
| 2143 |
-
|
| 2144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2145 |
if fname in (matched_filename, "sprite.json"):
|
| 2146 |
print(f" Skipping {fname} (matched image or sprite.json)")
|
| 2147 |
continue
|
| 2148 |
-
|
| 2149 |
-
|
| 2150 |
-
if os.path.isfile(src):
|
| 2151 |
try:
|
| 2152 |
-
shutil.copy2(
|
| 2153 |
-
print(f" β Copied sprite asset: {
|
| 2154 |
except Exception as e:
|
| 2155 |
-
print(f" β Failed to copy sprite asset {
|
| 2156 |
else:
|
| 2157 |
print(f" Skipping {fname} (not a file)")
|
| 2158 |
-
|
| 2159 |
-
#
|
| 2160 |
-
|
| 2161 |
-
|
| 2162 |
-
|
| 2163 |
-
|
| 2164 |
-
print("
|
| 2165 |
-
|
| 2166 |
# copy matched backdrop image
|
| 2167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2168 |
try:
|
| 2169 |
-
|
| 2170 |
-
print(f" β Copied matched backdrop image: {matched_image_path} -> {backdrop_dst}")
|
| 2171 |
except Exception as e:
|
| 2172 |
-
|
| 2173 |
-
|
| 2174 |
-
|
| 2175 |
-
|
| 2176 |
-
|
| 2177 |
-
|
| 2178 |
if fname in (matched_filename, "project.json"):
|
| 2179 |
print(f" Skipping {fname} (matched image or project.json)")
|
| 2180 |
continue
|
| 2181 |
-
|
| 2182 |
-
|
| 2183 |
-
src = Path(matched_folder) / fname
|
| 2184 |
-
dst = Path(project_folder) / fname
|
| 2185 |
-
if os.path.isfile(src):
|
| 2186 |
try:
|
| 2187 |
-
shutil.copy2(
|
| 2188 |
-
print(f" β Copied backdrop asset: {
|
| 2189 |
except Exception as e:
|
| 2190 |
-
print(f" β Failed to copy backdrop asset {
|
| 2191 |
else:
|
| 2192 |
print(f" Skipping {fname} (not a file)")
|
| 2193 |
-
|
| 2194 |
# read project.json to extract Stage/targets
|
| 2195 |
-
|
| 2196 |
-
pj
|
| 2197 |
-
if os.path.exists(pj):
|
| 2198 |
try:
|
| 2199 |
-
with open(
|
| 2200 |
bd_json = json.load(f)
|
| 2201 |
stage_count = 0
|
| 2202 |
for tgt in bd_json.get("targets", []):
|
| 2203 |
if tgt.get("isStage"):
|
| 2204 |
backdrop_data.append(tgt)
|
| 2205 |
stage_count += 1
|
| 2206 |
-
print(f" β Successfully read project.json from {
|
| 2207 |
except Exception as e:
|
| 2208 |
-
print(f" β Failed to read project.json in {
|
| 2209 |
else:
|
| 2210 |
-
print(f" β No project.json in {
|
| 2211 |
-
|
| 2212 |
print("---")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2213 |
|
| 2214 |
# --- Merge into final Scratch project.json (identical logic to before)
|
| 2215 |
final_project = {
|
|
|
|
| 50 |
phash = PHash()
|
| 51 |
gis = ImageSignature()
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
load_dotenv()
|
| 54 |
# os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY")
|
| 55 |
groq_api_key = os.getenv("GROQ_API_KEY")
|
|
|
|
| 62 |
|
| 63 |
app = Flask(__name__)
|
| 64 |
|
| 65 |
+
backdrop_images_path = r"app\blocks\Backdrops"
|
| 66 |
+
sprite_images_path = r"app\blocks\sprites"
|
| 67 |
+
code_blocks_image_path = r"app\blocks\code_blocks"
|
| 68 |
+
|
| 69 |
count = 0
|
| 70 |
|
| 71 |
+
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
| 72 |
BASE_DIR = Path(os.getenv("APP_BASE_DIR", Path(__file__).resolve().parent)) # fallback to code location
|
| 73 |
+
LOGS_DIR = Path(os.getenv("LOGS_DIR", "/tmp/logs")).resolve()
|
| 74 |
+
LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
| 75 |
for d in (BASE_DIR / "blocks", BASE_DIR / "static", BASE_DIR / "generated_projects", LOGS_DIR, BASE_DIR / "outputs"):
|
| 76 |
d.mkdir(parents=True, exist_ok=True)
|
| 77 |
+
# adjust BLOCKS_DIR etc:
|
| 78 |
+
BLOCKS_DIR = BASE_DIR / "blocks"
|
| 79 |
+
BACKDROP_DIR = BLOCKS_DIR / "Backdrops"
|
| 80 |
+
SPRITE_DIR = BLOCKS_DIR / "sprites"
|
| 81 |
+
CODE_BLOCKS_DIR = BLOCKS_DIR / "code_blocks"
|
|
|
|
| 82 |
|
| 83 |
+
# BASE_DIR = Path("/app")
|
| 84 |
+
# BLOCKS_DIR = BASE_DIR / "blocks"
|
| 85 |
+
# STATIC_DIR = BASE_DIR / "static"
|
| 86 |
+
# GEN_PROJECT_DIR = BASE_DIR / "generated_projects"
|
| 87 |
+
# BACKDROP_DIR = BLOCKS_DIR / "Backdrops"
|
| 88 |
+
# SPRITE_DIR = BLOCKS_DIR / "sprites"
|
| 89 |
+
# CODE_BLOCKS_DIR = BLOCKS_DIR / "code_blocks"
|
| 90 |
+
# # === new: outputs rooted under BASE_DIR ===
|
| 91 |
+
# OUTPUT_DIR = BASE_DIR / "outputs"
|
| 92 |
|
| 93 |
# Global variables to hold the model and index, loaded only once.
|
| 94 |
MODEL = None
|
|
|
|
| 106 |
OUTPUT_DIR,
|
| 107 |
):
|
| 108 |
d.mkdir(parents=True, exist_ok=True)
|
| 109 |
+
|
| 110 |
+
def log_execution_time(func):
|
| 111 |
+
@functools.wraps(func)
|
| 112 |
+
def wrapper(*args, **kwargs):
|
| 113 |
+
start_time = time.time()
|
| 114 |
+
result = func(*args, **kwargs)
|
| 115 |
+
end_time = time.time()
|
| 116 |
+
logger.info(f"β± {func.__name__} executed in {end_time - start_time:.2f} seconds")
|
| 117 |
+
return result
|
| 118 |
+
return wrapper
|
| 119 |
+
|
| 120 |
+
# Configure logging
|
| 121 |
+
logging.basicConfig(
|
| 122 |
+
level=logging.INFO,
|
| 123 |
+
format="%(asctime)s [%(levelname)s] %(message)s",
|
| 124 |
+
handlers=[
|
| 125 |
+
logging.FileHandler(str(LOGS_DIR / "app.log")),
|
| 126 |
+
logging.StreamHandler()
|
| 127 |
+
]
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
logger = logging.getLogger(__name__)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
class GameState(TypedDict):
|
| 134 |
project_json: dict
|
| 135 |
description: str
|
|
|
|
| 1435 |
import json
|
| 1436 |
os.makedirs(project_folder, exist_ok=True)
|
| 1437 |
|
| 1438 |
+
backdrop_base_path = os.path.normpath(str(BACKDROP_DIR))
|
| 1439 |
+
sprite_base_path = os.path.normpath(str(SPRITE_DIR))
|
| 1440 |
+
code_blocks_path = os.path.normpath(str(CODE_BLOCKS_DIR))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1441 |
|
| 1442 |
+
project_json_path = os.path.join(project_folder, "project.json")
|
| 1443 |
|
| 1444 |
# -------------------------
|
| 1445 |
# Build sprite images list (BytesIO) from sprites_data
|
|
|
|
| 1948 |
|
| 1949 |
# print("---")
|
| 1950 |
# add at top of file
|
| 1951 |
+
import shutil
|
| 1952 |
+
import json
|
| 1953 |
+
import os
|
| 1954 |
+
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1955 |
|
| 1956 |
+
# normalize base paths once before the loop
|
| 1957 |
+
sprite_base_p = Path(sprite_base_path).resolve(strict=False)
|
| 1958 |
+
backdrop_base_p = Path(backdrop_base_path).resolve(strict=False)
|
| 1959 |
+
project_folder_p = Path(project_folder)
|
| 1960 |
+
project_folder_p.mkdir(parents=True, exist_ok=True)
|
| 1961 |
|
| 1962 |
+
copied_sprite_folders = set()
|
| 1963 |
+
copied_backdrop_folders = set()
|
| 1964 |
|
| 1965 |
+
def display_like_windows_no_lead(p: Path) -> str:
|
| 1966 |
+
"""
|
| 1967 |
+
For human-readable logs only β convert Path to a string like:
|
| 1968 |
+
"app\\blocks\\Backdrops\\Castle 2.sb3" (no leading slash).
|
| 1969 |
+
"""
|
| 1970 |
+
s = p.as_posix() # forward-slash string, safe for Path objects
|
| 1971 |
+
if s.startswith("/"):
|
| 1972 |
+
s = s[1:]
|
| 1973 |
+
return s.replace("/", "\\")
|
| 1974 |
|
| 1975 |
+
def is_subpath(child: Path, parent: Path) -> bool:
|
| 1976 |
+
"""Robust membership test: is child under parent?"""
|
| 1977 |
+
try:
|
| 1978 |
+
# use non-strict resolve only if needed, but avoid exceptions
|
| 1979 |
+
child.relative_to(parent)
|
| 1980 |
+
return True
|
| 1981 |
+
except Exception:
|
| 1982 |
+
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1983 |
|
| 1984 |
+
# Flatten unique matched indices (if not already)
|
| 1985 |
+
matched_indices = sorted({idx for lst in per_sprite_matched_indices for idx in lst})
|
| 1986 |
+
print("matched_indices------------------>", matched_indices)
|
|
|
|
|
|
|
|
|
|
| 1987 |
|
| 1988 |
+
for matched_idx in matched_indices:
|
| 1989 |
+
# defensive check
|
| 1990 |
+
if not (0 <= matched_idx < len(paths_list)):
|
| 1991 |
+
print(f" β matched_idx {matched_idx} out of range, skipping")
|
| 1992 |
+
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1993 |
|
| 1994 |
+
matched_image_path = paths_list[matched_idx]
|
| 1995 |
+
matched_path_p = Path(matched_image_path).resolve(strict=False) # keep as Path
|
| 1996 |
+
matched_folder_p = matched_path_p.parent # Path object
|
| 1997 |
+
matched_filename = matched_path_p.name
|
|
|
|
|
|
|
| 1998 |
|
| 1999 |
+
# Prepare display-only string (do NOT reassign matched_folder_p)
|
| 2000 |
+
matched_folder_display = display_like_windows_no_lead(matched_folder_p)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2001 |
|
| 2002 |
+
print(f"Processing matched image: {matched_image_path}")
|
| 2003 |
+
print(f" - Folder: {matched_folder_display}")
|
| 2004 |
+
print(f" - Sprite path: {display_like_windows_no_lead(sprite_base_p)}")
|
| 2005 |
+
print(f" - Backdrop path: {display_like_windows_no_lead(backdrop_base_p)}")
|
| 2006 |
+
print(f" - Filename: {matched_filename}")
|
|
|
|
| 2007 |
|
| 2008 |
+
# Use a canonical string to store in the copied set (POSIX absolute-ish)
|
| 2009 |
+
folder_key = matched_folder_p.as_posix()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2010 |
|
| 2011 |
+
# ---------- SPRITE ----------
|
| 2012 |
+
if is_subpath(matched_folder_p, sprite_base_p) and folder_key not in copied_sprite_folders:
|
| 2013 |
+
print(f"Processing SPRITE folder: {matched_folder_display}")
|
| 2014 |
+
copied_sprite_folders.add(folder_key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2015 |
|
| 2016 |
+
sprite_json_path = matched_folder_p / "sprite.json"
|
| 2017 |
+
print("sprite_json_path----------------------->", sprite_json_path)
|
| 2018 |
+
print("copied sprite folder----------------------->", copied_sprite_folders)
|
| 2019 |
+
if sprite_json_path.exists() and sprite_json_path.is_file():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2020 |
try:
|
| 2021 |
+
with sprite_json_path.open("r", encoding="utf-8") as f:
|
| 2022 |
sprite_info = json.load(f)
|
| 2023 |
project_data.append(sprite_info)
|
| 2024 |
+
print(f" β Successfully read sprite.json from {matched_folder_display}")
|
| 2025 |
except Exception as e:
|
| 2026 |
+
print(f" β Failed to read sprite.json in {matched_folder_display}: {repr(e)}")
|
| 2027 |
else:
|
| 2028 |
+
print(f" β No sprite.json in {matched_folder_display}")
|
| 2029 |
+
|
| 2030 |
+
# copy non-matching files from the sprite folder (except matched image and sprite.json)
|
| 2031 |
+
try:
|
| 2032 |
+
sprite_files = list(matched_folder_p.iterdir())
|
| 2033 |
+
except Exception as e:
|
| 2034 |
+
sprite_files = []
|
| 2035 |
+
print(f" β Failed to list files in {matched_folder_display}: {repr(e)}")
|
| 2036 |
+
|
| 2037 |
+
print(f" Files in sprite folder: {[p.name for p in sprite_files]}")
|
| 2038 |
+
for p in sprite_files:
|
| 2039 |
+
fname = p.name
|
| 2040 |
if fname in (matched_filename, "sprite.json"):
|
| 2041 |
print(f" Skipping {fname} (matched image or sprite.json)")
|
| 2042 |
continue
|
| 2043 |
+
if p.is_file():
|
| 2044 |
+
dst = project_folder_p / fname
|
|
|
|
| 2045 |
try:
|
| 2046 |
+
shutil.copy2(str(p), str(dst))
|
| 2047 |
+
print(f" β Copied sprite asset: {p} -> {dst}")
|
| 2048 |
except Exception as e:
|
| 2049 |
+
print(f" β Failed to copy sprite asset {p}: {repr(e)}")
|
| 2050 |
else:
|
| 2051 |
print(f" Skipping {fname} (not a file)")
|
| 2052 |
+
|
| 2053 |
+
# ---------- BACKDROP ----------
|
| 2054 |
+
if is_subpath(matched_folder_p, backdrop_base_p) and folder_key not in copied_backdrop_folders:
|
| 2055 |
+
print(f"Processing BACKDROP folder: {matched_folder_display}")
|
| 2056 |
+
copied_backdrop_folders.add(folder_key)
|
| 2057 |
+
print("backdrop_base_path----------------------->", display_like_windows_no_lead(backdrop_base_p))
|
| 2058 |
+
print("copied backdrop folder----------------------->", copied_backdrop_folders)
|
| 2059 |
+
|
| 2060 |
# copy matched backdrop image
|
| 2061 |
+
backdrop_src = matched_folder_p / matched_filename
|
| 2062 |
+
backdrop_dst = project_folder_p / matched_filename
|
| 2063 |
+
if backdrop_src.exists() and backdrop_src.is_file():
|
| 2064 |
+
try:
|
| 2065 |
+
shutil.copy2(str(backdrop_src), str(backdrop_dst))
|
| 2066 |
+
print(f" β Copied matched backdrop image: {backdrop_src} -> {backdrop_dst}")
|
| 2067 |
+
except Exception as e:
|
| 2068 |
+
print(f" β Failed to copy matched backdrop image {backdrop_src}: {repr(e)}")
|
| 2069 |
+
else:
|
| 2070 |
+
print(f" β Matched backdrop source not found: {backdrop_src}")
|
| 2071 |
+
|
| 2072 |
+
# copy other files from folder (skip project.json and matched image)
|
| 2073 |
try:
|
| 2074 |
+
backdrop_files = list(matched_folder_p.iterdir())
|
|
|
|
| 2075 |
except Exception as e:
|
| 2076 |
+
backdrop_files = []
|
| 2077 |
+
print(f" β Failed to list files in {matched_folder_display}: {repr(e)}")
|
| 2078 |
+
|
| 2079 |
+
print(f" Files in backdrop folder: {[p.name for p in backdrop_files]}")
|
| 2080 |
+
for p in backdrop_files:
|
| 2081 |
+
fname = p.name
|
| 2082 |
if fname in (matched_filename, "project.json"):
|
| 2083 |
print(f" Skipping {fname} (matched image or project.json)")
|
| 2084 |
continue
|
| 2085 |
+
if p.is_file():
|
| 2086 |
+
dst = project_folder_p / fname
|
|
|
|
|
|
|
|
|
|
| 2087 |
try:
|
| 2088 |
+
shutil.copy2(str(p), str(dst))
|
| 2089 |
+
print(f" β Copied backdrop asset: {p} -> {dst}")
|
| 2090 |
except Exception as e:
|
| 2091 |
+
print(f" β Failed to copy backdrop asset {p}: {repr(e)}")
|
| 2092 |
else:
|
| 2093 |
print(f" Skipping {fname} (not a file)")
|
| 2094 |
+
|
| 2095 |
# read project.json to extract Stage/targets
|
| 2096 |
+
pj = matched_folder_p / "project.json"
|
| 2097 |
+
if pj.exists() and pj.is_file():
|
|
|
|
| 2098 |
try:
|
| 2099 |
+
with pj.open("r", encoding="utf-8") as f:
|
| 2100 |
bd_json = json.load(f)
|
| 2101 |
stage_count = 0
|
| 2102 |
for tgt in bd_json.get("targets", []):
|
| 2103 |
if tgt.get("isStage"):
|
| 2104 |
backdrop_data.append(tgt)
|
| 2105 |
stage_count += 1
|
| 2106 |
+
print(f" β Successfully read project.json from {matched_folder_display}, found {stage_count} stage(s)")
|
| 2107 |
except Exception as e:
|
| 2108 |
+
print(f" β Failed to read project.json in {matched_folder_display}: {repr(e)}")
|
| 2109 |
else:
|
| 2110 |
+
print(f" β No project.json in {matched_folder_display}")
|
| 2111 |
+
|
| 2112 |
print("---")
|
| 2113 |
+
# for matched_idx in matched_indices:
|
| 2114 |
+
# matched_image_path = paths_list[matched_idx]
|
| 2115 |
+
# matched_folder = os.path.dirname(matched_image_path)
|
| 2116 |
+
# matched_filename = os.path.basename(matched_image_path)
|
| 2117 |
+
|
| 2118 |
+
# print(f"Processing matched image: {matched_image_path}")
|
| 2119 |
+
# print(f" - Folder: {matched_folder}")
|
| 2120 |
+
# print(f" - Sprite path: {sprite_base_path}")
|
| 2121 |
+
# print(f" - Backdrop path: {backdrop_base_path}")
|
| 2122 |
+
# print(f" - Filename: {matched_filename}")
|
| 2123 |
+
|
| 2124 |
+
# # If it's a sprite (under SPRITE_DIR) -> copy sprite assets and read sprite.json
|
| 2125 |
+
# if matched_folder.startswith(sprite_base_path) and matched_folder not in copied_sprite_folders:
|
| 2126 |
+
# print(f"Processing SPRITE folder: {matched_folder}")
|
| 2127 |
+
# copied_sprite_folders.add(matched_folder)
|
| 2128 |
+
# sprite_json_path = os.path.join(matched_folder, "sprite.json")
|
| 2129 |
+
# print("sprite_json_path----------------------->",sprite_json_path)
|
| 2130 |
+
# print("copied sprite folder----------------------->",copied_sprite_folders)
|
| 2131 |
+
# if os.path.exists(sprite_json_path):
|
| 2132 |
+
# try:
|
| 2133 |
+
# with open(sprite_json_path, "r", encoding="utf-8") as f:
|
| 2134 |
+
# sprite_info = json.load(f)
|
| 2135 |
+
# project_data.append(sprite_info)
|
| 2136 |
+
# print(f" β Successfully read sprite.json from {matched_folder}")
|
| 2137 |
+
# except Exception as e:
|
| 2138 |
+
# print(f" β Failed to read sprite.json in {matched_folder}: {e}")
|
| 2139 |
+
# else:
|
| 2140 |
+
# print(f" β No sprite.json in {matched_folder}")
|
| 2141 |
+
|
| 2142 |
+
# # copy non-matching files from the sprite folder (except the matched image and sprite.json)
|
| 2143 |
+
# sprite_files = os.listdir(matched_folder)
|
| 2144 |
+
# print(f" Files in sprite folder: {sprite_files}")
|
| 2145 |
+
# for fname in sprite_files:
|
| 2146 |
+
# if fname in (matched_filename, "sprite.json"):
|
| 2147 |
+
# print(f" Skipping {fname} (matched image or sprite.json)")
|
| 2148 |
+
# continue
|
| 2149 |
+
# src = os.path.join(matched_folder, fname)
|
| 2150 |
+
# dst = os.path.join(project_folder, fname)
|
| 2151 |
+
# if os.path.isfile(src):
|
| 2152 |
+
# try:
|
| 2153 |
+
# shutil.copy2(src, dst)
|
| 2154 |
+
# print(f" β Copied sprite asset: {src} -> {dst}")
|
| 2155 |
+
# except Exception as e:
|
| 2156 |
+
# print(f" β Failed to copy sprite asset {src}: {e}")
|
| 2157 |
+
# else:
|
| 2158 |
+
# print(f" Skipping {fname} (not a file)")
|
| 2159 |
+
|
| 2160 |
+
# # If it's a backdrop (under BACKDROP_DIR) -> copy backdrop assets and read project.json for stage
|
| 2161 |
+
# if matched_folder.startswith(backdrop_base_path) and matched_folder not in copied_backdrop_folders:
|
| 2162 |
+
# print(f"Processing BACKDROP folder: {matched_folder}")
|
| 2163 |
+
# copied_backdrop_folders.add(matched_folder)
|
| 2164 |
+
# print("backdrop_base_path----------------------->",backdrop_base_path)
|
| 2165 |
+
# print("copied backdrop folder----------------------->",copied_backdrop_folders)
|
| 2166 |
+
# # copy matched backdrop image
|
| 2167 |
+
# backdrop_dst = os.path.join(project_folder, matched_filename)
|
| 2168 |
+
# try:
|
| 2169 |
+
# shutil.copy2(matched_image_path, backdrop_dst)
|
| 2170 |
+
# print(f" β Copied matched backdrop image: {matched_image_path} -> {backdrop_dst}")
|
| 2171 |
+
# except Exception as e:
|
| 2172 |
+
# print(f" β Failed to copy matched backdrop image {matched_image_path}: {e}")
|
| 2173 |
+
|
| 2174 |
+
# # copy other files from folder (skip project.json and matched image)
|
| 2175 |
+
# backdrop_files = os.listdir(matched_folder)
|
| 2176 |
+
# print(f" Files in backdrop folder: {backdrop_files}")
|
| 2177 |
+
# for fname in backdrop_files:
|
| 2178 |
+
# if fname in (matched_filename, "project.json"):
|
| 2179 |
+
# print(f" Skipping {fname} (matched image or project.json)")
|
| 2180 |
+
# continue
|
| 2181 |
+
# src = os.path.join(matched_folder, fname)
|
| 2182 |
+
# dst = os.path.join(project_folder, fname)
|
| 2183 |
+
# if os.path.isfile(src):
|
| 2184 |
+
# try:
|
| 2185 |
+
# shutil.copy2(src, dst)
|
| 2186 |
+
# print(f" β Copied backdrop asset: {src} -> {dst}")
|
| 2187 |
+
# except Exception as e:
|
| 2188 |
+
# print(f" β Failed to copy backdrop asset {src}: {e}")
|
| 2189 |
+
# else:
|
| 2190 |
+
# print(f" Skipping {fname} (not a file)")
|
| 2191 |
+
|
| 2192 |
+
# # read project.json to extract Stage/targets
|
| 2193 |
+
# pj = os.path.join(matched_folder, "project.json")
|
| 2194 |
+
# if os.path.exists(pj):
|
| 2195 |
+
# try:
|
| 2196 |
+
# with open(pj, "r", encoding="utf-8") as f:
|
| 2197 |
+
# bd_json = json.load(f)
|
| 2198 |
+
# stage_count = 0
|
| 2199 |
+
# for tgt in bd_json.get("targets", []):
|
| 2200 |
+
# if tgt.get("isStage"):
|
| 2201 |
+
# backdrop_data.append(tgt)
|
| 2202 |
+
# stage_count += 1
|
| 2203 |
+
# print(f" β Successfully read project.json from {matched_folder}, found {stage_count} stage(s)")
|
| 2204 |
+
# except Exception as e:
|
| 2205 |
+
# print(f" β Failed to read project.json in {matched_folder}: {e}")
|
| 2206 |
+
# else:
|
| 2207 |
+
# print(f" β No project.json in {matched_folder}")
|
| 2208 |
+
|
| 2209 |
+
# print("---")
|
| 2210 |
|
| 2211 |
# --- Merge into final Scratch project.json (identical logic to before)
|
| 2212 |
final_project = {
|