Files
novafarma/tools/organize_tilesets.py

330 lines
9.5 KiB
Python

"""
🗺️ TILED BATCH TILESET ORGANIZER
==================================
Organizes 122 sprite sheets into categorized TSX files for Tiled Map Editor
Author: Antigravity AI
Date: 2025-12-22
Project: Krvava Žetev / NovaFarma
"""
import os
import shutil
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)
# Category definitions with sprite sheet mappings
CATEGORIES = {
"01_Characters_NPCs": [
"kai_character_2x2_grid",
"ana_character_2x2_grid",
"lena_farmer_2x2_grid",
"marija_baker_2x2_grid",
"ivan_blacksmith_2x2_grid",
"jakob_trader_2x2_grid",
"dr_chen_doctor_2x2_grid",
"dr_krnic_villain_2x2_grid"
],
"02_Animals_Pets": [
"farm_animals_family_grid",
"dog_companions_5_breeds",
"rare_livestock_animals",
"delivery_creatures_bat_owl"
],
"03_Buildings_Upgrades": [
"house_progression_5_stages",
"barn_progression_4_stages",
"storage_upgrades_4_stages",
"greenhouse_upgrades_stages",
"buildings_ruins_states"
],
"04_Environment_Terrain": [
"biome_terrain_tiles",
"trees_topdown_pack",
"grass_soil_tileset",
"seasonal_vegetation_4_seasons",
"tree_growth_oak",
"tree_growth_pine"
],
"05_Crops_Farming": [
"crop_growth_all",
"wheat_growth",
"seasonal_crops_grid",
"seasonal_seed_packets",
"fruit_trees_growth"
],
"06_Weapons_Combat": [
"weapons_melee_grid",
"weapons_firearms_modern",
"bows_5_types",
"arrows_9_types_elemental"
],
"07_Crafting_Blueprints": [
"blueprints_building_unlocks",
"crafting_recipes_ui_display",
"legendary_blueprint_golden"
],
"08_Transport_Systems": [
"train_repairs_3_states",
"horses_breeds_5_types",
"cart_wagon_system",
"water_transport_pack"
],
"09_Magic_System": [
"magic_staffs_6_types",
"spell_effects_animations",
"potions_elixirs_grid"
],
"10_DLC_Dino_World": [
"dinosaurs_animation_strips",
"dino_world_clothing",
"dino_world_food",
"dino_world_items"
],
"11_DLC_Mythical_Highlands": [
"mythical_creatures_pack",
"highland_clothing",
"highland_food",
"highland_items"
],
"12_DLC_Amazon": [
"amazon_creatures_pack",
"amazon_clothing",
"amazon_food",
"amazon_items"
],
"13_DLC_Egypt": [
"egyptian_structures_pack",
"egyptian_pyramids_sphinx",
"egypt_clothing",
"egypt_food"
],
"14_DLC_Atlantis": [
"atlantis_objects_pack",
"atlantis_clothing",
"atlantis_food",
"atlantis_items"
],
"15_DLC_Chernobyl": [
"chernobyl_structures_pack",
"chernobyl_clothing",
"chernobyl_food",
"anomalous_creatures_detailed"
],
"16_DLC_Paris": [
"paris_catacombs_pack",
"paris_clothing",
"paris_food"
],
"17_DLC_Loch_Ness": [
"loch_ness_creatures",
"scotland_clothing",
"scotland_food"
],
"18_Monsters_Bosses": [
"slimes_8_types_pack",
"giant_troll_king_boss",
"grok_fabulous_complete_sprite",
"werewolf_moon_phases"
],
"19_Furniture_Interior": [
"starter_house_interior",
"bedroom_furniture_pack",
"kitchen_furniture_pack",
"living_room_pack"
],
"20_Misc_Items": [
"backpack_upgrades_6_tiers",
"tools_farming_grid",
"fishing_equipment_pack",
"anas_story_clues_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 (approx - Tiled will correct on load)
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 category
if "character" in png_path.stem.lower() or "npc" in png_path.stem.lower():
tile_width, tile_height = 96, 96 # 2x2 grid on 48px base
elif "building" in png_path.stem.lower() or "house" in png_path.stem.lower():
tile_width, tile_height = 192, 192 # Larger buildings
else:
tile_width, tile_height = 48, 48 # Default tile size
# Calculate columns and tile count
columns = width // tile_width
rows = height // tile_height
tilecount = columns * rows
if tilecount == 0:
tilecount = 1 # Single tile
columns = 1
# 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"
with open(tsx_file, 'wb') as f:
f.write(pretty_xml)
print(f"✅ Created: {tsx_file.relative_to(BASE_DIR)}")
return tsx_file
def find_matching_sprite(sprite_pattern, sprite_dir):
"""
Finds PNG files matching a pattern in the sprite directory
Args:
sprite_pattern: Pattern to search for (e.g., "kai_character_2x2")
sprite_dir: Directory to search in
Returns:
List of matching PNG files
"""
matches = []
for png_file in sprite_dir.glob("*.png"):
if sprite_pattern.lower() in png_file.stem.lower():
matches.append(png_file)
return matches
def organize_tilesets():
"""
Main function to organize all sprite sheets into categorized TSX files
"""
print("🗺️ TILED BATCH TILESET ORGANIZER")
print("=" * 60)
print(f"📂 Source: {KRVAVA_SPRITES_DIR}")
print(f"📂 Output: {TILESETS_OUTPUT_DIR}")
print("=" * 60)
print()
total_processed = 0
total_found = 0
for category, sprite_patterns in CATEGORIES.items():
print(f"\n📁 {category}")
print("-" * 60)
for pattern in sprite_patterns:
# Find matching sprites
matches = find_matching_sprite(pattern, KRVAVA_SPRITES_DIR)
if not matches:
print(f" ⚠️ No match for: {pattern}")
continue
total_found += len(matches)
# Create TSX for each match
for png_path in matches:
tsx_created = create_tsx_from_png(png_path, category, TILESETS_OUTPUT_DIR)
if tsx_created:
total_processed += 1
print("\n" + "=" * 60)
print(f"✅ COMPLETE!")
print(f"📊 Found: {total_found} sprite sheets")
print(f"📊 Processed: {total_processed} TSX files")
print(f"📂 Output: {TILESETS_OUTPUT_DIR}")
print("=" * 60)
print()
print("🎯 NEXT STEPS:")
print("1. Open Tiled Map Editor")
print("2. Open your map (e.g., micro_farm_128x128.tmx)")
print("3. Map → Add External Tileset...")
print("4. Navigate to assets/maps/organized_tilesets/")
print("5. Select category folder and import TSX files")
print("=" * 60)
def create_category_readme():
"""
Creates a README in each category folder explaining contents
"""
for category, patterns in CATEGORIES.items():
category_dir = TILESETS_OUTPUT_DIR / category
readme_path = category_dir / "README.md"
content = f"# {category.replace('_', ' ').title()}\n\n"
content += "## Sprite Sheets Included:\n\n"
for pattern in patterns:
content += f"- `{pattern}`\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"
if category_dir.exists():
with open(readme_path, 'w', encoding='utf-8') as f:
f.write(content)
if __name__ == "__main__":
try:
organize_tilesets()
create_category_readme()
except Exception as e:
print(f"❌ ERROR: {e}")
import traceback
traceback.print_exc()