#!/usr/bin/env python3 """ Generate catalogue.json from Unreal Engine assets. This script can be run: 1. Standalone with Python (parses .uasset files for metadata) 2. As a wrapper for UE commandlet (for full asset inspection) Usage: python generate_catalogue.py --project /path/to/Ralpha.uproject python generate_catalogue.py --scan-only # Just scan folders """ import argparse import json import os import subprocess import hashlib from datetime import datetime from pathlib import Path from typing import Any # Asset categories based on folder structure CATEGORY_MAP = { "Furniture/Seating": ["sofa", "chair", "stool", "bench", "armchair"], "Furniture/Tables": ["table", "desk", "counter"], "Furniture/Storage": ["shelf", "cabinet", "drawer", "wardrobe"], "Furniture/Bedroom": ["bed", "nightstand", "mattress"], "Electronics/Computing": ["laptop", "monitor", "keyboard", "mouse", "computer"], "Electronics/Entertainment": ["tv", "speaker", "console", "headphone"], "Electronics/Appliances": ["refrigerator", "microwave", "oven", "washer"], "Food/Packaged": ["can", "bottle", "box", "bag", "jar"], "Food/Fresh": ["fruit", "vegetable", "bread", "meat"], "Food/Prepared": ["pizza", "burger", "sandwich", "cake"], "Food/Beverages": ["coffee", "tea", "juice", "wine", "beer"], "Vehicles/Cars": ["sedan", "suv", "truck", "van", "sports"], "Vehicles/Motorcycles": ["motorcycle", "scooter", "bike"], "Nature/Trees": ["tree", "pine", "oak", "palm"], "Nature/Plants": ["plant", "flower", "bush", "grass"], "Nature/Rocks": ["rock", "boulder", "stone", "cliff"], } def scan_asset_folder(content_path: Path) -> list[dict[str, Any]]: """Scan Content/Ralpha/Assets for .uasset files.""" assets = [] asset_root = content_path / "Ralpha" / "Assets" if not asset_root.exists(): print(f"Warning: Asset folder not found: {asset_root}") return assets for uasset_path in asset_root.rglob("*.uasset"): # Skip generated files if "Intermediate" in str(uasset_path) or "Saved" in str(uasset_path): continue rel_path = uasset_path.relative_to(content_path) category = str(rel_path.parent).replace("Ralpha/Assets/", "").replace("/", "/") # Generate stable ID from path asset_name = uasset_path.stem asset_id = asset_name.lower().replace(" ", "_").replace("-", "_") # Build Unreal path unreal_path = f"/Game/{str(rel_path.with_suffix('')).replace(os.sep, '/')}" # Infer tags from name tags = [word.lower() for word in asset_name.replace("_", " ").split()] asset = { "id": asset_id, "name": asset_name.replace("_", " ").title(), "category": category, "unreal_path": unreal_path, "tags": tags, "thumbnail": f"thumbnails/{asset_id}.png", "pivot": "bottom_center", "placement_modes": ["floor"], "collision": True, "license": { "type": "Unknown", "source": "TBD" }, "added_at": datetime.utcnow().isoformat() + "Z" } assets.append(asset) return assets def run_ue_commandlet(project_path: Path, ue_path: Path | None = None) -> list[dict[str, Any]]: """Run UE commandlet to extract full asset metadata.""" # This would invoke the UE editor in commandlet mode # For now, just use scan method print("Note: Full UE commandlet integration not implemented yet") print("Using folder scan method instead") content_path = project_path.parent / "Content" return scan_asset_folder(content_path) def generate_thumbnails(assets: list[dict], project_path: Path) -> None: """Generate thumbnail images for assets (placeholder).""" thumbnail_dir = project_path.parent / "RalphaData" / "thumbnails" thumbnail_dir.mkdir(parents=True, exist_ok=True) print(f"Thumbnail directory: {thumbnail_dir}") print("Note: Actual thumbnail generation requires UE Editor Utility") # Create placeholder file placeholder = thumbnail_dir / ".gitkeep" placeholder.touch() def compute_catalogue_hash(assets: list[dict]) -> str: """Compute hash of catalogue for version tracking.""" content = json.dumps(sorted([a["id"] for a in assets])) return hashlib.sha256(content.encode()).hexdigest()[:12] def main(): parser = argparse.ArgumentParser(description="Generate Ralpha asset catalogue") parser.add_argument("--project", type=Path, help="Path to .uproject file") parser.add_argument("--scan-only", action="store_true", help="Only scan folders, no UE") parser.add_argument("--output", type=Path, help="Output catalogue path") parser.add_argument("--ue-path", type=Path, help="Path to UE Editor executable") args = parser.parse_args() # Find project if args.project: project_path = args.project else: # Default to current directory project_files = list(Path.cwd().glob("*.uproject")) if project_files: project_path = project_files[0] else: print("Error: No .uproject file found. Use --project to specify.") return 1 print(f"Project: {project_path}") # Scan or run commandlet content_path = project_path.parent / "Content" if args.scan_only or not args.ue_path: assets = scan_asset_folder(content_path) else: assets = run_ue_commandlet(project_path, args.ue_path) print(f"Found {len(assets)} assets") # Build catalogue catalogue = { "$schema": "./catalogue.schema.json", "version": "1.0.0", "generated_at": datetime.utcnow().isoformat() + "Z", "asset_count": len(assets), "catalogue_hash": compute_catalogue_hash(assets), "categories": sorted(list(set(a["category"] for a in assets))), "assets": sorted(assets, key=lambda a: (a["category"], a["id"])) } # Output if args.output: output_path = args.output else: output_path = project_path.parent / "RalphaData" / "catalogue" / "catalogue.json" output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, "w") as f: json.dump(catalogue, f, indent=2) print(f"Catalogue written to: {output_path}") print(f"Hash: {catalogue['catalogue_hash']}") # Generate thumbnails placeholder generate_thumbnails(assets, project_path) return 0 if __name__ == "__main__": exit(main())