extract_mid_level.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import argparse
  2. import sys
  3. import json
  4. import shutil
  5. from pathlib import Path
  6. # Add parent directories to the Python path to find utils and parsers
  7. source_path = Path(__file__).parent.parent
  8. sys.path.append(str(source_path / 'utils'))
  9. sys.path.append(str(source_path / 'parsers'))
  10. from file_utils import replicate_directory_structure, find_files_by_extension, create_guid_to_path_map
  11. from json_utils import write_json
  12. from config_utils import load_config
  13. from scene_processor import UnitySceneProcessor
  14. def generate_guid_mappers(input_dir, output_dir, indent=None, shrink=False, ignored_folders=None):
  15. """
  16. Finds all .meta files and generates JSON files mapping GUIDs to asset paths.
  17. """
  18. assets_dir = input_dir / "Assets"
  19. if not assets_dir.is_dir():
  20. print(f"Error: 'Assets' directory not found in '{input_dir}'", file=sys.stderr)
  21. return
  22. print("--> Finding all .meta files...")
  23. meta_files = find_files_by_extension(str(assets_dir), '.meta', ignored_folders=ignored_folders, project_root=input_dir)
  24. print(f"--> Found {len(meta_files)} .meta files to process.")
  25. asset_type_map = {
  26. '.prefab': 'prefabs', '.unity': 'scenes', '.mat': 'materials',
  27. '.cs': 'scripts', '.png': 'textures', '.jpg': 'textures',
  28. '.jpeg': 'textures', '.asset': 'scriptable_objects',
  29. }
  30. guid_maps = {value: {} for value in asset_type_map.values()}
  31. guid_maps['others'] = {}
  32. print("--> Parsing .meta files and mapping GUIDs to asset paths...")
  33. total_files = len(meta_files)
  34. for i, meta_file_path_str in enumerate(meta_files):
  35. if (i + 1) % 250 == 0:
  36. print(f" ...processed {i+1}/{total_files} .meta files...")
  37. meta_file_path = Path(meta_file_path_str)
  38. asset_file_path = Path(meta_file_path_str.rsplit('.meta', 1)[0])
  39. if not asset_file_path.is_file():
  40. continue
  41. guid = None
  42. try:
  43. with open(meta_file_path, 'r', encoding='utf-8') as f:
  44. for line in f:
  45. if line.strip().startswith('guid:'):
  46. guid = line.strip().split(':')[1].strip()
  47. break
  48. except Exception as e:
  49. print(f"Warning: Could not read or parse guid from {meta_file_path}. {e}", file=sys.stderr)
  50. continue
  51. if guid:
  52. asset_ext = asset_file_path.suffix.lower()
  53. asset_type = asset_type_map.get(asset_ext, 'others')
  54. # Use the full path from the project root for the guid_map value
  55. full_asset_path = input_dir / asset_file_path
  56. guid_maps[asset_type][guid] = full_asset_path.as_posix()
  57. print(f" ...finished processing all {total_files} .meta files.")
  58. mappers_dir = output_dir / "GuidMappers"
  59. try:
  60. mappers_dir.mkdir(parents=True, exist_ok=True)
  61. print(f"--> Writing GUID mapper files to {mappers_dir}...")
  62. for asset_type, guid_map in guid_maps.items():
  63. if guid_map:
  64. output_path = mappers_dir / f"{asset_type}.json"
  65. # For the output JSON, we still want the project-relative path
  66. relative_guid_map = {g: Path(p).relative_to(input_dir).as_posix() for g, p in guid_map.items()}
  67. write_json(relative_guid_map, output_path, indent=indent, shrink=shrink)
  68. print(f" -> Created {asset_type}.json with {len(guid_map)} entries.")
  69. print(f"--> Successfully created all GUID mappers.")
  70. except OSError as e:
  71. print(f"Error: Could not create GUID mapper directory or files. {e}", file=sys.stderr)
  72. # Return the map with full paths for the processor
  73. full_path_guid_map = {}
  74. for asset_type in guid_maps:
  75. full_path_guid_map.update(guid_maps[asset_type])
  76. return full_path_guid_map
  77. def main():
  78. """
  79. Main function to run the mid-level data extraction process.
  80. This script generates a virtual file structure and detailed GUID mappers.
  81. """
  82. parser = argparse.ArgumentParser(
  83. description="Generates a virtual representation of the project's structure and GUID maps."
  84. )
  85. parser.add_argument(
  86. "--input",
  87. type=str,
  88. required=True,
  89. help="The root directory of the target Unity project."
  90. )
  91. parser.add_argument(
  92. "--output",
  93. type=str,
  94. required=True,
  95. help="The directory where the generated output folder will be saved."
  96. )
  97. args = parser.parse_args()
  98. # --- Load Configuration ---
  99. config = load_config()
  100. ignored_folders = config.get('ignored_folders', [])
  101. shrink_json = config.get('shrink_json', False)
  102. indent_level = config.get('indentation_level', 4)
  103. input_dir = Path(args.input).resolve()
  104. output_dir = Path(args.output).resolve()
  105. if not input_dir.is_dir():
  106. print(f"Error: Input path '{input_dir}' is not a valid directory.", file=sys.stderr)
  107. sys.exit(1)
  108. # --- Setup Output Directories ---
  109. mid_level_output_dir = output_dir / "MidLevel"
  110. output_assets_dir = mid_level_output_dir / "Assets"
  111. try:
  112. output_assets_dir.mkdir(parents=True, exist_ok=True)
  113. print(f"Output will be saved to: {mid_level_output_dir}")
  114. except OSError as e:
  115. print(f"Error: Could not create output directory '{mid_level_output_dir}'. {e}", file=sys.stderr)
  116. sys.exit(1)
  117. assets_dir = input_dir / "Assets"
  118. if not assets_dir.is_dir():
  119. print(f"Warning: 'Assets' directory not found in '{input_dir}'. Skipping all processing.", file=sys.stderr)
  120. return
  121. print("\n--- Running Mid-Level Extraction ---")
  122. # --- Task 1: Replicate 'Assets' directory structure ---
  123. print("\n[1/3] Replicating 'Assets' directory structure...")
  124. replicate_directory_structure(str(assets_dir), str(output_assets_dir), ignored_folders=ignored_folders, project_root=input_dir)
  125. print("--> Directory structure replication complete.")
  126. # --- Task 2: Generate GUID Map and Mappers ---
  127. print("\n[2/3] Generating GUID Mappers...")
  128. guid_map = generate_guid_mappers(
  129. input_dir,
  130. mid_level_output_dir,
  131. indent=indent_level,
  132. shrink=shrink_json,
  133. ignored_folders=ignored_folders
  134. )
  135. print("--> GUID Mapper generation complete.")
  136. # --- Task 3: Orchestrate Scene and Prefab Parsing for Hierarchy ---
  137. print("\n[3/3] Parsing Scene and Prefab Hierarchies...")
  138. print("--> Finding scene and prefab files...")
  139. scene_files = find_files_by_extension(str(assets_dir), '.unity', ignored_folders=ignored_folders, project_root=input_dir)
  140. prefab_files = find_files_by_extension(str(assets_dir), '.prefab', ignored_folders=ignored_folders, project_root=input_dir)
  141. files_to_process = scene_files + prefab_files
  142. print(f"--> Found {len(files_to_process)} total scene/prefab files to process.")
  143. total_files = len(files_to_process)
  144. for i, file_path_str in enumerate(files_to_process):
  145. file_path = Path(file_path_str)
  146. relative_path = file_path.relative_to(assets_dir)
  147. output_json_path = (output_assets_dir / relative_path).with_suffix('.json')
  148. try:
  149. print(f"\n--- Processing {file_path.name} ({i+1}/{total_files}) ---")
  150. # Use the sophisticated processor for building the visual tree
  151. processor = UnitySceneProcessor(guid_map)
  152. print(f" -> Loading and parsing file...")
  153. if not processor.load_documents(file_path):
  154. print(f"Warning: Could not load or parse {file_path.name}. Skipping.", file=sys.stderr)
  155. continue
  156. print(f" -> Pass 1/6: Building relationship maps and creating basic nodes...")
  157. processor.process_first_pass()
  158. print(f" -> Pass 2/6: Building hierarchy relationships...")
  159. processor.process_second_pass()
  160. print(f" -> Pass 3/6: Verifying and fixing parent-child relationships...")
  161. processor.verification_pass()
  162. print(f" -> Pass 4/6: Extracting components...")
  163. processor.process_third_pass()
  164. print(f" -> Pass 5/6: Merging prefab data...")
  165. processor.merge_prefab_data_pass()
  166. print(f" -> Pass 6/6: Assembling final hierarchy...")
  167. hierarchy = processor.get_hierarchy()
  168. output_json_path.parent.mkdir(parents=True, exist_ok=True)
  169. write_json(hierarchy, output_json_path, indent=indent_level, shrink=shrink_json)
  170. print(f"--> Successfully processed hierarchy for {file_path.name} -> {output_json_path}")
  171. except Exception as e:
  172. print(f"Error processing hierarchy for {file_path.name}: {e}", file=sys.stderr)
  173. print("\nMid-level extraction complete.")
  174. if __name__ == "__main__":
  175. main()