scene_processor.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. import argparse
  2. import sys
  3. import json
  4. from pathlib import Path
  5. # Add the utils directory to the Python path
  6. utils_path = Path(__file__).parent / 'utils'
  7. sys.path.append(str(utils_path))
  8. from yaml_utils import load_unity_yaml, convert_to_plain_python_types
  9. class UnitySceneProcessor:
  10. def __init__(self, guid_map):
  11. self.guid_map = guid_map
  12. self.object_map = {}
  13. self.nodes = {}
  14. self.prefab_nodes = {}
  15. self.transform_to_gameobject = {}
  16. self.gameobject_to_transform = {}
  17. self.transform_children = {}
  18. self.stripped_gameobjects = {}
  19. self.processed_relationships = set()
  20. def load_prefab_data(self, prefab_guid):
  21. """Load and parse prefab data from GUID"""
  22. if not prefab_guid or prefab_guid not in self.guid_map:
  23. return {}
  24. prefab_path = self.guid_map[prefab_guid]
  25. try:
  26. documents = load_unity_yaml(prefab_path)
  27. if not documents:
  28. return {}
  29. raw_object_map = {int(doc.anchor.value): doc for doc in documents if hasattr(doc, 'anchor') and doc.anchor is not None}
  30. return {file_id: convert_to_plain_python_types(obj) for file_id, obj in raw_object_map.items()}
  31. except Exception:
  32. return {}
  33. def calculate_deep_sibling_index(self, scene_transform_id, prefab_guid):
  34. """Calculate deep sibling index for scene objects that are children of prefab objects"""
  35. scene_transform = self.object_map.get(scene_transform_id, {}).get('Transform', {})
  36. scene_root_order = scene_transform.get('m_RootOrder', 0)
  37. # Get parent transform (should be stripped)
  38. parent_transform_id = scene_transform.get('m_Father', {}).get('fileID')
  39. if not parent_transform_id:
  40. return str(scene_root_order)
  41. parent_transform = self.object_map.get(parent_transform_id, {}).get('Transform', {})
  42. corresponding_source = parent_transform.get('m_CorrespondingSourceObject', {})
  43. prefab_transform_id = corresponding_source.get('fileID')
  44. if not prefab_transform_id:
  45. return str(scene_root_order)
  46. # Load prefab data and traverse hierarchy
  47. prefab_data = self.load_prefab_data(prefab_guid)
  48. if not prefab_data:
  49. return str(scene_root_order)
  50. # Build sibling index by traversing up the prefab hierarchy
  51. sibling_indices = []
  52. current_transform_id = prefab_transform_id
  53. while current_transform_id and current_transform_id in prefab_data:
  54. transform_data = prefab_data.get(current_transform_id, {}).get('Transform', {})
  55. if not transform_data:
  56. break
  57. root_order = transform_data.get('m_RootOrder', 0)
  58. sibling_indices.insert(0, str(root_order))
  59. # Move to parent
  60. parent_id = transform_data.get('m_Father', {}).get('fileID')
  61. if not parent_id or parent_id == 0:
  62. break
  63. current_transform_id = parent_id
  64. # Add the scene object's own root order at the end
  65. sibling_indices.append(str(scene_root_order))
  66. return '-'.join(sibling_indices)
  67. def process_first_pass(self):
  68. """First pass: Build relationship maps and create basic nodes"""
  69. stripped_transforms = {}
  70. stripped_gameobjects = {}
  71. prefab_instances = {}
  72. for file_id, obj_data in self.object_map.items():
  73. if 'GameObject' in obj_data:
  74. go_info = obj_data['GameObject']
  75. # Always create a node for a GameObject.
  76. # If it's part of a prefab, we'll link it later.
  77. self.nodes[file_id] = {
  78. 'fileID': str(file_id),
  79. 'm_Name': go_info.get('m_Name', 'Unknown'),
  80. 'm_IsActive': go_info.get('m_IsActive', 1),
  81. 'm_TagString': go_info.get('m_TagString', 'Untagged'),
  82. 'm_Layer': go_info.get('m_Layer', 0),
  83. 'components': [],
  84. 'children': []
  85. }
  86. # If it's a stripped GameObject, track it for component linking
  87. is_stripped = any('stripped' in str(key) for key in obj_data.keys() if hasattr(key, '__str__'))
  88. if is_stripped:
  89. prefab_instance_id = go_info.get('m_PrefabInstance', {}).get('fileID')
  90. if prefab_instance_id:
  91. stripped_gameobjects[file_id] = {
  92. 'prefab_instance_id': prefab_instance_id,
  93. 'm_CorrespondingSourceObject': go_info.get('m_CorrespondingSourceObject', {})
  94. }
  95. # If it's the root GameObject of a prefab instance, add a link for merging
  96. prefab_instance_id = go_info.get('m_PrefabInstance', {}).get('fileID')
  97. if prefab_instance_id and not is_stripped:
  98. self.nodes[file_id]['prefab_instance_id'] = prefab_instance_id
  99. elif 'PrefabInstance' in obj_data:
  100. prefab_info = obj_data['PrefabInstance']
  101. source_prefab = prefab_info.get('m_SourcePrefab', {})
  102. modifications = prefab_info.get('m_Modification', {})
  103. # Extract m_IsActive, m_Name, and m_RootOrder from modifications
  104. m_is_active = 1 # default
  105. m_name = None
  106. m_root_order = 999999 # default
  107. for mod in modifications.get('m_Modifications', []):
  108. if mod.get('propertyPath') == 'm_IsActive':
  109. m_is_active = mod.get('value', 1)
  110. elif mod.get('propertyPath') == 'm_Name':
  111. m_name = mod.get('value')
  112. elif mod.get('propertyPath') == 'm_RootOrder':
  113. m_root_order = mod.get('value', 999999)
  114. prefab_instances[file_id] = {
  115. 'source_prefab_guid': source_prefab.get('guid'),
  116. 'm_TransformParent': modifications.get('m_TransformParent', {}).get('fileID'),
  117. 'm_IsActive': m_is_active,
  118. 'm_Name': m_name,
  119. 'm_RootOrder': m_root_order
  120. }
  121. elif 'Transform' in obj_data:
  122. transform_info = obj_data['Transform']
  123. # Check if this is a stripped transform (has m_PrefabInstance)
  124. prefab_instance_id = transform_info.get('m_PrefabInstance', {}).get('fileID')
  125. if prefab_instance_id:
  126. stripped_transforms[file_id] = {
  127. 'prefab_instance_id': prefab_instance_id,
  128. 'm_CorrespondingSourceObject': transform_info.get('m_CorrespondingSourceObject', {})
  129. }
  130. # Build transform mappings
  131. gameobject_id = transform_info.get('m_GameObject', {}).get('fileID')
  132. if gameobject_id:
  133. self.transform_to_gameobject[file_id] = gameobject_id
  134. self.gameobject_to_transform[gameobject_id] = file_id
  135. # Build parent-child relationships
  136. parent_id = transform_info.get('m_Father', {}).get('fileID')
  137. if parent_id and parent_id != 0:
  138. if parent_id not in self.transform_children:
  139. self.transform_children[parent_id] = []
  140. self.transform_children[parent_id].append(file_id)
  141. # Create prefab nodes
  142. for prefab_id, prefab_info in prefab_instances.items():
  143. self.prefab_nodes[prefab_id] = {
  144. 'fileID': str(prefab_id),
  145. 'source_prefab_guid': prefab_info['source_prefab_guid'],
  146. 'm_IsActive': prefab_info['m_IsActive'],
  147. 'm_RootOrder': prefab_info.get('m_RootOrder', 999999),
  148. 'components': [], # Will be populated with override components
  149. 'children': []
  150. }
  151. if prefab_info.get('m_Name'):
  152. self.prefab_nodes[prefab_id]['m_Name'] = prefab_info['m_Name']
  153. # Store for second pass
  154. self.stripped_transforms = stripped_transforms
  155. self.stripped_gameobjects = stripped_gameobjects
  156. self.prefab_instances = prefab_instances
  157. def process_second_pass(self):
  158. """Second pass: Build hierarchy relationships"""
  159. # 1. Handle standard GameObject parent-child relationships
  160. for go_id, node in self.nodes.items():
  161. transform_id = self.gameobject_to_transform.get(go_id)
  162. if not transform_id:
  163. continue
  164. # Get child transforms
  165. child_transform_ids = self.transform_children.get(transform_id, [])
  166. for child_transform_id in child_transform_ids:
  167. # Check if child transform is stripped (part of prefab)
  168. if child_transform_id in self.stripped_transforms:
  169. stripped_info = self.stripped_transforms[child_transform_id]
  170. prefab_instance_id = stripped_info['prefab_instance_id']
  171. if prefab_instance_id in self.prefab_nodes:
  172. relationship_key = f"{go_id}->{prefab_instance_id}"
  173. if relationship_key not in self.processed_relationships:
  174. node['children'].append(self.prefab_nodes[prefab_instance_id])
  175. self.processed_relationships.add(relationship_key)
  176. else:
  177. # Regular GameObject child
  178. child_go_id = self.transform_to_gameobject.get(child_transform_id)
  179. if child_go_id and child_go_id in self.nodes:
  180. relationship_key = f"{go_id}->{child_go_id}"
  181. if relationship_key not in self.processed_relationships:
  182. node['children'].append(self.nodes[child_go_id])
  183. self.processed_relationships.add(relationship_key)
  184. # 2. Handle prefab-to-parent relationships
  185. for prefab_id, prefab_info in self.prefab_instances.items():
  186. parent_transform_id = prefab_info['m_TransformParent']
  187. if not parent_transform_id or parent_transform_id == 0:
  188. continue # Root prefab, will be handled in get_root_objects
  189. # Find parent GameObject or parent prefab
  190. parent_go_id = self.transform_to_gameobject.get(parent_transform_id)
  191. if parent_go_id and parent_go_id in self.nodes:
  192. # Prefab child of GameObject
  193. relationship_key = f"{parent_go_id}->{prefab_id}"
  194. if relationship_key not in self.processed_relationships:
  195. self.nodes[parent_go_id]['children'].append(self.prefab_nodes[prefab_id])
  196. self.processed_relationships.add(relationship_key)
  197. elif parent_transform_id in self.stripped_transforms:
  198. # Prefab child of another prefab
  199. stripped_info = self.stripped_transforms[parent_transform_id]
  200. parent_prefab_id = stripped_info['prefab_instance_id']
  201. if parent_prefab_id in self.prefab_nodes:
  202. relationship_key = f"{parent_prefab_id}->{prefab_id}"
  203. if relationship_key not in self.processed_relationships:
  204. self.prefab_nodes[parent_prefab_id]['children'].append(self.prefab_nodes[prefab_id])
  205. self.processed_relationships.add(relationship_key)
  206. # 3. Handle scene objects as children of prefabs (with deep sibling index)
  207. for go_id, node in self.nodes.items():
  208. transform_id = self.gameobject_to_transform.get(go_id)
  209. if not transform_id:
  210. continue
  211. # Find parent transform
  212. parent_transform_id = None
  213. for parent_id, children in self.transform_children.items():
  214. if transform_id in children:
  215. parent_transform_id = parent_id
  216. break
  217. if parent_transform_id and parent_transform_id in self.stripped_transforms:
  218. stripped_info = self.stripped_transforms[parent_transform_id]
  219. prefab_instance_id = stripped_info['prefab_instance_id']
  220. if prefab_instance_id in self.prefab_nodes:
  221. # Calculate deep sibling index
  222. prefab_guid = self.prefab_instances[prefab_instance_id]['source_prefab_guid']
  223. deep_sibling_index = self.calculate_deep_sibling_index(transform_id, prefab_guid)
  224. if deep_sibling_index:
  225. node['deep_sibling_index'] = deep_sibling_index
  226. # Add as child of prefab
  227. relationship_key = f"{prefab_instance_id}->{go_id}"
  228. if relationship_key not in self.processed_relationships:
  229. self.prefab_nodes[prefab_instance_id]['children'].append(node)
  230. self.processed_relationships.add(relationship_key)
  231. def process_third_pass(self):
  232. """Third pass: Extract components for both GameObjects and PrefabInstances"""
  233. for file_id, obj_data in self.object_map.items():
  234. # Skip GameObjects, Transforms, and PrefabInstances
  235. if any(key in obj_data for key in ['GameObject', 'Transform', 'PrefabInstance']):
  236. continue
  237. # Find the GameObject this component belongs to
  238. gameobject_id = None
  239. for key, data in obj_data.items():
  240. if isinstance(data, dict) and 'm_GameObject' in data:
  241. gameobject_id = data['m_GameObject'].get('fileID')
  242. break
  243. if not gameobject_id:
  244. continue
  245. component_name = next((key for key in obj_data if key not in ['m_ObjectHideFlags']), 'Unknown')
  246. # Handle MonoBehaviour with script GUID
  247. if component_name == 'MonoBehaviour':
  248. script_info = obj_data['MonoBehaviour'].get('m_Script', {})
  249. script_guid = script_info.get('guid')
  250. if script_guid:
  251. component_entry = script_guid
  252. else:
  253. component_entry = "MonoBehaviour"
  254. else:
  255. component_entry = component_name
  256. # Check if this component belongs to a regular GameObject
  257. if gameobject_id in self.nodes:
  258. if component_entry not in self.nodes[gameobject_id]['components']:
  259. self.nodes[gameobject_id]['components'].append(component_entry)
  260. # Check if this component belongs to a stripped GameObject (prefab override)
  261. elif gameobject_id in self.stripped_gameobjects:
  262. stripped_info = self.stripped_gameobjects[gameobject_id]
  263. prefab_instance_id = stripped_info['prefab_instance_id']
  264. if prefab_instance_id in self.prefab_nodes:
  265. if component_entry not in self.prefab_nodes[prefab_instance_id]['components']:
  266. self.prefab_nodes[prefab_instance_id]['components'].append(component_entry)
  267. # If neither, this component belongs to an unknown GameObject (potential orphan)
  268. else:
  269. print(f"Warning: Component {component_name} (ID: {file_id}) references unknown GameObject {gameobject_id}")
  270. def verification_pass(self):
  271. """
  272. Verifies and fixes parent-child relationships that might have been missed
  273. by using a direct child->parent lookup. This acts as a patch for the
  274. less reliable reverse-lookup used in process_second_pass.
  275. """
  276. for go_id, node in list(self.nodes.items()):
  277. transform_id = self.gameobject_to_transform.get(go_id)
  278. if not transform_id:
  279. continue
  280. transform_info = self.object_map.get(transform_id, {}).get('Transform', {})
  281. parent_transform_id = transform_info.get('m_Father', {}).get('fileID')
  282. if not parent_transform_id or parent_transform_id == 0:
  283. continue # This is a root object, no parent to verify.
  284. # Case 1: Parent is a PrefabInstance (via a stripped transform)
  285. if parent_transform_id in self.stripped_transforms:
  286. stripped_info = self.stripped_transforms[parent_transform_id]
  287. parent_prefab_id = stripped_info['prefab_instance_id']
  288. if parent_prefab_id in self.prefab_nodes:
  289. relationship_key = f"{parent_prefab_id}->{go_id}"
  290. if relationship_key not in self.processed_relationships:
  291. # Add deep sibling index if it's a child of a prefab
  292. prefab_guid = self.prefab_instances[parent_prefab_id]['source_prefab_guid']
  293. deep_sibling_index = self.calculate_deep_sibling_index(transform_id, prefab_guid)
  294. if deep_sibling_index:
  295. node['deep_sibling_index'] = deep_sibling_index
  296. self.prefab_nodes[parent_prefab_id]['children'].append(node)
  297. self.processed_relationships.add(relationship_key)
  298. # Case 2: Parent is a regular GameObject
  299. else:
  300. parent_go_id = self.transform_to_gameobject.get(parent_transform_id)
  301. if parent_go_id and parent_go_id in self.nodes:
  302. relationship_key = f"{parent_go_id}->{go_id}"
  303. if relationship_key not in self.processed_relationships:
  304. self.nodes[parent_go_id]['children'].append(node)
  305. self.processed_relationships.add(relationship_key)
  306. def merge_prefab_data_pass(self):
  307. """Fourth pass: Merge GameObject data into their corresponding PrefabInstance nodes"""
  308. nodes_to_delete = []
  309. for go_id, go_node in self.nodes.items():
  310. prefab_instance_id = go_node.get('prefab_instance_id')
  311. if not prefab_instance_id:
  312. continue
  313. if prefab_instance_id in self.prefab_nodes:
  314. # Merge the GameObject data into the PrefabInstance node
  315. # The prefab node's existing data takes precedence
  316. prefab_node = self.prefab_nodes[prefab_instance_id]
  317. # Create a copy of the gameobject node to avoid modifying during iteration
  318. go_node_copy = go_node.copy()
  319. # Remove keys that should not be merged or are already handled
  320. del go_node_copy['fileID']
  321. del go_node_copy['prefab_instance_id']
  322. # Update prefab_node with go_node_copy data
  323. # This will overwrite keys in prefab_node with values from go_node_copy
  324. # if they exist in both. We want the opposite.
  325. # Let's do it manually to ensure prefab data is kept
  326. for key, value in go_node_copy.items():
  327. if key not in prefab_node:
  328. prefab_node[key] = value
  329. # Specifically handle children and components to merge them
  330. prefab_node['children'].extend(go_node_copy.get('children', []))
  331. # Merge components, avoiding duplicates
  332. existing_components = set(str(c) for c in prefab_node.get('components', []))
  333. for comp in go_node_copy.get('components', []):
  334. if str(comp) not in existing_components:
  335. prefab_node['components'].append(comp)
  336. # Rename 'components' to 'addedComponents' for clarity
  337. if 'components' in prefab_node:
  338. prefab_node['addedComponents'] = prefab_node.pop('components')
  339. # Mark the original GameObject node for deletion
  340. nodes_to_delete.append(go_id)
  341. # Remove the now-redundant GameObject nodes
  342. for go_id in nodes_to_delete:
  343. del self.nodes[go_id]
  344. def get_root_objects(self):
  345. """Get all root-level objects, sorted by m_RootOrder, and handle orphans"""
  346. root_objects = []
  347. all_children_ids = set()
  348. # Collect all child IDs to identify orphans
  349. for go_id, node in self.nodes.items():
  350. self._collect_child_ids(node, all_children_ids)
  351. for prefab_id, prefab_node in self.prefab_nodes.items():
  352. self._collect_child_ids(prefab_node, all_children_ids)
  353. # Find GameObjects that have no parent and collect their m_RootOrder
  354. gameobject_roots = []
  355. for go_id, node in self.nodes.items():
  356. transform_id = self.gameobject_to_transform.get(go_id)
  357. if not transform_id:
  358. # No transform - likely orphan
  359. if go_id not in all_children_ids:
  360. node['is_orphan'] = True
  361. gameobject_roots.append((node, 999999)) # Orphans go to end
  362. continue
  363. # Check if this transform has a parent
  364. has_parent = False
  365. for parent_id, children in self.transform_children.items():
  366. if transform_id in children:
  367. has_parent = True
  368. break
  369. # Also check if it's a child of any prefab
  370. is_prefab_child = False
  371. for parent_transform_id in self.stripped_transforms:
  372. if transform_id in self.transform_children.get(parent_transform_id, []):
  373. is_prefab_child = True
  374. break
  375. if not has_parent and not is_prefab_child:
  376. if go_id not in all_children_ids:
  377. # Get m_RootOrder from transform
  378. root_order = self._get_root_order(transform_id)
  379. gameobject_roots.append((node, root_order))
  380. else:
  381. # This is an orphan that was somehow referenced but not properly parented
  382. node['is_orphan'] = True
  383. gameobject_roots.append((node, 999999)) # Orphans go to end
  384. # Find root prefab instances and collect their m_RootOrder
  385. prefab_roots = []
  386. for prefab_id, prefab_info in self.prefab_instances.items():
  387. parent_transform_id = prefab_info['m_TransformParent']
  388. if not parent_transform_id or parent_transform_id == 0:
  389. if prefab_id in self.prefab_nodes:
  390. if prefab_id not in all_children_ids:
  391. # Get m_RootOrder directly from the prefab node
  392. root_order = self.prefab_nodes[prefab_id].get('m_RootOrder', 999999)
  393. prefab_roots.append((self.prefab_nodes[prefab_id], root_order))
  394. else:
  395. # Orphan prefab
  396. self.prefab_nodes[prefab_id]['is_orphan'] = True
  397. prefab_roots.append((self.prefab_nodes[prefab_id], 999999)) # Orphans go to end
  398. # Check for completely disconnected GameObjects (orphans)
  399. for go_id, node in self.nodes.items():
  400. if go_id not in all_children_ids and not any(obj[0].get('fileID') == str(go_id) for obj in gameobject_roots):
  401. node['is_orphan'] = True
  402. gameobject_roots.append((node, 999999)) # Orphans go to end
  403. # Check for completely disconnected PrefabInstances (orphans)
  404. for prefab_id, prefab_node in self.prefab_nodes.items():
  405. if prefab_id not in all_children_ids and not any(obj[0].get('fileID') == str(prefab_id) for obj in prefab_roots):
  406. prefab_node['is_orphan'] = True
  407. prefab_roots.append((prefab_node, 999999)) # Orphans go to end
  408. # Combine and sort all root objects by m_RootOrder
  409. all_roots = gameobject_roots + prefab_roots
  410. all_roots.sort(key=lambda x: x[1]) # Sort by m_RootOrder (second element of tuple)
  411. return [obj[0] for obj in all_roots] # Return only the objects, not the tuples
  412. def _get_root_order(self, transform_id):
  413. """Get m_RootOrder from a transform"""
  414. if transform_id not in self.object_map:
  415. return 999999 # Default for missing transforms
  416. transform_data = self.object_map[transform_id].get('Transform', {})
  417. return transform_data.get('m_RootOrder', 999999)
  418. def _collect_child_ids(self, node, child_ids_set):
  419. """Recursively collect all child IDs from a node tree"""
  420. for child in node.get('children', []):
  421. child_id = child.get('fileID')
  422. if child_id:
  423. # Convert to int for comparison with our keys
  424. try:
  425. child_ids_set.add(int(child_id))
  426. except ValueError:
  427. pass
  428. self._collect_child_ids(child, child_ids_set)
  429. def cleanup_pass(self, nodes):
  430. """
  431. Recursively cleans up temporary or internal properties from the final node structure.
  432. """
  433. # List of properties to remove from the final output
  434. cleanup_keys = ['m_RootOrder']
  435. for node in nodes:
  436. for key in cleanup_keys:
  437. if key in node:
  438. del node[key]
  439. if 'children' in node and node['children']:
  440. self.cleanup_pass(node['children'])
  441. def process_file(self, file_path):
  442. """Main processing method"""
  443. # Load and parse the file
  444. documents = load_unity_yaml(file_path)
  445. if not documents:
  446. return []
  447. # Build object map
  448. raw_object_map = {int(doc.anchor.value): doc for doc in documents if hasattr(doc, 'anchor') and doc.anchor is not None}
  449. self.object_map = {file_id: convert_to_plain_python_types(obj) for file_id, obj in raw_object_map.items()}
  450. # Process in passes
  451. self.process_first_pass()
  452. self.process_second_pass()
  453. self.verification_pass()
  454. self.process_third_pass()
  455. self.merge_prefab_data_pass()
  456. # Get the final, sorted root objects
  457. root_objects = self.get_root_objects()
  458. # Run the final cleanup pass
  459. self.cleanup_pass(root_objects)
  460. return root_objects
  461. def main():
  462. parser = argparse.ArgumentParser(description="Process Unity scene/prefab files into JSON representation")
  463. parser.add_argument("--input", required=True, help="Path to input .unity or .prefab file")
  464. parser.add_argument("--guid-map", required=True, help="Path to guid_map.json file")
  465. parser.add_argument("--output", required=True, help="Path to output .json file")
  466. args = parser.parse_args()
  467. input_path = Path(args.input)
  468. guid_map_path = Path(args.guid_map)
  469. output_path = Path(args.output)
  470. if not input_path.exists():
  471. print(f"Error: Input file {input_path} does not exist", file=sys.stderr)
  472. sys.exit(1)
  473. if not guid_map_path.exists():
  474. print(f"Error: GUID map file {guid_map_path} does not exist", file=sys.stderr)
  475. sys.exit(1)
  476. # Load GUID map
  477. try:
  478. with open(guid_map_path, 'r', encoding='utf-8') as f:
  479. guid_map = json.load(f)
  480. except Exception as e:
  481. print(f"Error loading GUID map: {e}", file=sys.stderr)
  482. sys.exit(1)
  483. # Process the file
  484. processor = UnitySceneProcessor(guid_map)
  485. try:
  486. result = processor.process_file(input_path)
  487. # Ensure output directory exists
  488. output_path.parent.mkdir(parents=True, exist_ok=True)
  489. # Write output
  490. with open(output_path, 'w', encoding='utf-8') as f:
  491. json.dump(result, f, indent=2, ensure_ascii=False)
  492. print(f"Successfully processed {input_path.name} -> {output_path}")
  493. except Exception as e:
  494. print(f"Error processing file {input_path}: {e}", file=sys.stderr)
  495. sys.exit(1)
  496. if __name__ == "__main__":
  497. main()