""" πŸ—ΊοΈ TILED COMPLETE TILESET ORGANIZER V2 ======================================== Processes ALL sprite sheets automatically and organizes into TSX files Author: Antigravity AI Date: 2025-12-22 Project: Krvava Ε½etev / NovaFarma """ import os from pathlib import Path import xml.etree.ElementTree as ET from xml.dom import minidom # Base paths BASE_DIR = Path(r"c:\novafarma\assets") KRVAVA_SPRITES_DIR = BASE_DIR / "krvava_zetev_sprites" TILESETS_OUTPUT_DIR = BASE_DIR / "maps" / "organized_tilesets" # Ensure output directory exists TILESETS_OUTPUT_DIR.mkdir(parents=True, exist_ok=True) def auto_categorize_sprite(filename): """ Automatically categorizes a sprite based on its filename Args: filename: Name of the PNG file (without extension) Returns: Category name """ fn = filename.lower() # Characters & NPCs if any(x in fn for x in ['character', 'npc', 'doctor', 'farmer', 'baker', 'blacksmith', 'trader', 'sonya', 'assistant', 'romance']): return "01_Characters_NPCs" # Animals & Pets if any(x in fn for x in ['animal', 'farm_animals', 'dog', 'livestock', 'delivery_creatures']): return "02_Animals_Pets" # Buildings & Structures if any(x in fn for x in ['building', 'house', 'barn', 'storage', 'greenhouse', 'mine', 'town_buildings', 'portal_structures', 'minting']): return "03_Buildings_Upgrades" # Environment & Terrain if any(x in fn for x in ['grass', 'soil', 'tileset', 'terrain', 'biome', 'fence', 'obstacles', 'vegetation']): return "04_Environment_Terrain" # Crops & Farming if any(x in fn for x in ['crop', 'wheat', 'seed', 'seasonal_seed', 'fruit_trees', 'sprinkler', 'children_5_growth']): return "05_Crops_Farming" # Weapons & Combat if any(x in fn for x in ['weapon', 'melee', 'firearms', 'bow', 'arrow']): return "06_Weapons_Combat" # Crafting & Blueprints if any(x in fn for x in ['blueprint', 'crafting', 'recipe', 'legendary']): return "07_Crafting_Blueprints" # Transport Systems if any(x in fn for x in ['train', 'horse', 'cart', 'wagon', 'transport', 'vehicle']): return "08_Transport_Systems" # Magic System if any(x in fn for x in ['magic', 'staff', 'spell', 'potion', 'elixir', 'portal_states']): return "09_Magic_System" # DLC: Dino World if any(x in fn for x in ['dinosaur', 'dino']): return "10_DLC_Dino_World" # DLC: Mythical Highlands if any(x in fn for x in ['mythical', 'highland']): return "11_DLC_Mythical_Highlands" # DLC: Amazon if any(x in fn for x in ['amazon']): return "12_DLC_Amazon" # DLC: Egypt if any(x in fn for x in ['egypt', 'pyramid', 'sphinx']): return "13_DLC_Egypt" # DLC: Atlantis if any(x in fn for x in ['atlantis']): return "14_DLC_Atlantis" # DLC: Chernobyl if any(x in fn for x in ['chernobyl', 'anomalous']): return "15_DLC_Chernobyl" # DLC: Paris if any(x in fn for x in ['paris', 'catacomb']): return "16_DLC_Paris" # DLC: Loch Ness if any(x in fn for x in ['loch', 'scotland']): return "17_DLC_Loch_Ness" # Monsters & Bosses if any(x in fn for x in ['zombie', 'slime', 'troll', 'grok', 'monster', 'boss']): return "18_Monsters_Bosses" # Furniture & Interior if any(x in fn for x in ['furniture', 'interior', 'bedroom', 'kitchen', 'living']): return "19_Furniture_Interior" # Misc Items (default) return "20_Misc_Items" def create_tsx_from_png(png_path, category_name, output_dir): """ Creates a Tiled TSX tileset file from a PNG sprite sheet Args: png_path: Path to the PNG file category_name: Category folder name output_dir: Output directory for TSX file """ if not png_path.exists(): print(f"⚠️ PNG not found: {png_path}") return None # Get image dimensions from PIL import Image try: img = Image.open(png_path) width, height = img.size img.close() except Exception as e: print(f"❌ Error reading image {png_path}: {e}") return None # Determine tile size based on filename and category fn_lower = png_path.stem.lower() if "2x2_grid" in fn_lower or "character" in fn_lower: tile_width, tile_height = 96, 96 # 2x2 grid on 48px base elif "building" in fn_lower or "house" in fn_lower or "barn" in fn_lower: tile_width, tile_height = 192, 192 # Larger buildings elif "tree" in fn_lower and "growth" not in fn_lower: tile_width, tile_height = 128, 128 # Trees else: tile_width, tile_height = 48, 48 # Default tile size # Calculate columns and tile count columns = max(1, width // tile_width) rows = max(1, height // tile_height) tilecount = columns * rows # Create TSX structure tileset = ET.Element("tileset", { "version": "1.10", "tiledversion": "1.11.1", "name": png_path.stem.replace("_", " ").title(), "tilewidth": str(tile_width), "tileheight": str(tile_height), "tilecount": str(tilecount), "columns": str(columns) }) # Relative path from TSX to PNG rel_path = os.path.relpath(png_path, output_dir).replace("\\", "/") image = ET.SubElement(tileset, "image", { "source": f"../../{rel_path}", "width": str(width), "height": str(height) }) # Pretty print XML rough_string = ET.tostring(tileset, encoding='unicode') reparsed = minidom.parseString(rough_string) pretty_xml = reparsed.toprettyxml(indent=" ", encoding="UTF-8") # Save TSX file category_output = output_dir / category_name category_output.mkdir(parents=True, exist_ok=True) tsx_file = category_output / f"{png_path.stem}.tsx" # Skip if already exists if tsx_file.exists(): print(f"⏭️ Skipped (exists): {png_path.stem}") return tsx_file with open(tsx_file, 'wb') as f: f.write(pretty_xml) print(f"βœ… Created: {category_name}/{png_path.stem}.tsx") return tsx_file def organize_all_tilesets(): """ Processes ALL PNG files in krvava_zetev_sprites directory """ print("πŸ—ΊοΈ TILED COMPLETE TILESET ORGANIZER V2") print("=" * 70) print(f"πŸ“‚ Source: {KRVAVA_SPRITES_DIR}") print(f"πŸ“‚ Output: {TILESETS_OUTPUT_DIR}") print("=" * 70) print() # Get all PNG files all_pngs = list(KRVAVA_SPRITES_DIR.glob("*.png")) print(f"πŸ“Š Found {len(all_pngs)} sprite sheets to process") print("=" * 70) print() category_counts = {} total_processed = 0 total_skipped = 0 total_errors = 0 for png_path in sorted(all_pngs): # Auto-categorize category = auto_categorize_sprite(png_path.stem) # Track category counts if category not in category_counts: category_counts[category] = 0 category_counts[category] += 1 # Create TSX tsx = create_tsx_from_png(png_path, category, TILESETS_OUTPUT_DIR) if tsx and not tsx.exists(): total_processed += 1 elif tsx and tsx.exists(): total_skipped += 1 else: total_errors += 1 # Summary print() print("=" * 70) print("βœ… PROCESSING COMPLETE!") print("=" * 70) print(f"πŸ“Š Total sprites: {len(all_pngs)}") print(f"βœ… Created: {total_processed} new TSX files") print(f"⏭️ Skipped: {total_skipped} (already exist)") print(f"❌ Errors: {total_errors}") print() print("πŸ“ Category Breakdown:") print("-" * 70) for category, count in sorted(category_counts.items()): print(f" {category:35} {count:3} files") print("=" * 70) print() print("🎯 NEXT STEPS:") print("1. Open Tiled Map Editor") print("2. Map β†’ Add External Tileset...") print("3. Navigate to: assets/maps/organized_tilesets/") print("4. Import TSX files by category") print("=" * 70) def create_category_readme(): """ Creates README in each category folder """ categories = set() # Find all categories used for category_dir in TILESETS_OUTPUT_DIR.iterdir(): if category_dir.is_dir(): categories.add(category_dir.name) for category in categories: category_dir = TILESETS_OUTPUT_DIR / category readme_path = category_dir / "README.md" # Count TSX files tsx_files = list(category_dir.glob("*.tsx")) content = f"# {category.replace('_', ' ').title()}\n\n" content += f"**Total Tilesets:** {len(tsx_files)}\n\n" content += "## Contents:\n\n" for tsx in sorted(tsx_files): content += f"- `{tsx.stem}`\n" content += "\n## Usage in Tiled:\n\n" content += "1. **Map β†’ Add External Tileset...**\n" content += "2. Select .tsx files from this folder\n" content += "3. Tilesets will appear in your Tilesets panel\n" content += "4. Select tiles and place on map!\n" with open(readme_path, 'w', encoding='utf-8') as f: f.write(content) print(f"πŸ“ Updated: {category}/README.md ({len(tsx_files)} files)") if __name__ == "__main__": try: organize_all_tilesets() print() create_category_readme() print() print("πŸŽ‰ ALL DONE!") except Exception as e: print(f"❌ ERROR: {e}") import traceback traceback.print_exc()