import argparse import sys import json import shutil from pathlib import Path # Add the utils directory to the Python path utils_path = Path(__file__).parent / 'utils' sys.path.append(str(utils_path)) from file_utils import find_files_by_extension from deep_parser import parse_scene_or_prefab from json_utils import write_json from yaml_utils import load_unity_yaml, convert_to_plain_python_types from hierarchy_utils import HierarchyParser def copy_scripts(assets_dir, output_assets_dir): """ Copies all C# scripts (.cs) to the target directory. """ print("\n--- Starting Script Handling ---") cs_files = find_files_by_extension(str(assets_dir), '.cs') print(f"Found {len(cs_files)} C# script files to copy.") for script_path_str in cs_files: script_path = Path(script_path_str) relative_path = script_path.relative_to(assets_dir) destination_path = output_assets_dir / relative_path destination_path.parent.mkdir(parents=True, exist_ok=True) try: shutil.copy(script_path, destination_path) except IOError as e: print(f"Error copying {script_path} to {destination_path}: {e}", file=sys.stderr) print("Script copying complete.") def main(): """ Main function to run the low-level data extraction process. """ parser = argparse.ArgumentParser( description="Generates a deeply detailed, per-GameObject breakdown of scenes and prefabs." ) parser.add_argument( "--input", type=str, required=True, help="The root directory of the target Unity project." ) parser.add_argument( "--output", type=str, required=True, help="The directory where the generated output folder will be saved." ) parser.add_argument( "--indent", type=int, default=None, help="Indentation level for JSON output. Defaults to None (compact)." ) args = parser.parse_args() input_dir = Path(args.input).resolve() output_dir = Path(args.output).resolve() if not input_dir.is_dir(): print(f"Error: Input path '{input_dir}' is not a valid directory.", file=sys.stderr) sys.exit(1) # Create the main output folder, named "LowLevel" low_level_output_dir = output_dir / "LowLevel" output_assets_dir = low_level_output_dir / "Assets" try: output_assets_dir.mkdir(parents=True, exist_ok=True) print(f"Output will be saved to: {low_level_output_dir}") except OSError as e: print(f"Error: Could not create output directory '{low_level_output_dir}'. {e}", file=sys.stderr) sys.exit(1) # --- Orchestration --- assets_dir = input_dir / "Assets" if not assets_dir.is_dir(): print(f"Error: 'Assets' directory not found in '{input_dir}'.", file=sys.stderr) return # --- Task 1: Copy C# Scripts --- copy_scripts(assets_dir, output_assets_dir) # --- Task 2: Process Scenes and Prefabs --- scene_files = find_files_by_extension(str(assets_dir), '.unity') prefab_files = find_files_by_extension(str(assets_dir), '.prefab') files_to_process = scene_files + prefab_files print(f"\nFound {len(files_to_process)} scene/prefab files to process.") for file_path_str in files_to_process: file_path = Path(file_path_str) print(f"\nProcessing Scene/Prefab: {file_path.name}") # --- Deep Parsing for Individual GameObjects --- gameobject_list = parse_scene_or_prefab(str(file_path)) # Create the output subdirectory for this asset relative_path = file_path.relative_to(input_dir) asset_output_dir = low_level_output_dir / relative_path try: asset_output_dir.mkdir(parents=True, exist_ok=True) except OSError as e: print(f"Error creating directory {asset_output_dir}: {e}", file=sys.stderr) continue if gameobject_list: print(f"Saving {len(gameobject_list)} GameObjects to {asset_output_dir}") for go_data in gameobject_list: file_id = go_data.get('fileID') if file_id: output_json_path = asset_output_dir / f"{file_id}.json" try: write_json(go_data, output_json_path, indent=args.indent) except Exception as e: print(f"Error writing to {output_json_path}: {e}", file=sys.stderr) else: print(f"Skipped deep parsing for {file_path.name}.") # --- Hierarchy Parsing for Root Object Identification --- try: documents = load_unity_yaml(file_path) if not documents: print(f"Could not load YAML from {file_path.name} for hierarchy parsing.") continue raw_object_map = {int(doc.anchor.value): doc for doc in documents if hasattr(doc, 'anchor') and doc.anchor is not None} object_map = {file_id: convert_to_plain_python_types(obj) for file_id, obj in raw_object_map.items()} parser = HierarchyParser(object_map) root_object_ids = parser.get_root_object_ids() root_ids_list = [file_id for file_id, _ in root_object_ids] if root_ids_list: roots_output_path = asset_output_dir / "root_objects.json" write_json(root_ids_list, roots_output_path, indent=args.indent) print(f"Successfully saved root object list to {roots_output_path}") except Exception as e: print(f"Error during hierarchy parsing for {file_path.name}: {e}", file=sys.stderr) # --- Task 3: Process .asset files --- asset_files = find_files_by_extension(str(assets_dir), '.asset') print(f"\nFound {len(asset_files)} .asset files to process.") for file_path_str in asset_files: file_path = Path(file_path_str) print(f"\nProcessing Asset: {file_path.name}") relative_path = file_path.relative_to(input_dir) asset_output_dir = low_level_output_dir / relative_path try: asset_output_dir.mkdir(parents=True, exist_ok=True) except OSError as e: print(f"Error creating directory {asset_output_dir}: {e}", file=sys.stderr) continue try: documents = load_unity_yaml(file_path) if not documents: print(f"Skipped {file_path.name} as it's empty or could not be parsed.") continue print(f"Saving {len(documents)} objects from {file_path.name} to {asset_output_dir}") for doc in documents: if not hasattr(doc, 'anchor') or doc.anchor is None: continue file_id = int(doc.anchor.value) obj_data = convert_to_plain_python_types(doc) final_obj_data = {} for key, value in obj_data.items(): if isinstance(value, dict): new_value = value.copy() new_value['fileID'] = file_id final_obj_data[key] = new_value else: final_obj_data[key] = value output_json_path = asset_output_dir / f"{file_id}.json" write_json(final_obj_data, output_json_path, indent=args.indent) except Exception as e: print(f"Error processing asset file {file_path.name}: {e}", file=sys.stderr) print("\nLow-level extraction complete.") if __name__ == "__main__": main()