| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 | 
							- import argparse
 
- import sys
 
- import json
 
- from pathlib import Path
 
- # Add the parent directory to the Python path to find utils
 
- utils_path = Path(__file__).parent.parent / 'utils'
 
- sys.path.append(str(utils_path))
 
- from yaml_utils import load_unity_yaml, convert_to_plain_python_types
 
- from hierarchy_utils import HierarchyParser
 
- class UnitySceneProcessor:
 
-     def __init__(self, guid_map):
 
-         self.guid_map = guid_map
 
-         self.object_map = {}
 
-         self.nodes = {}
 
-         self.prefab_nodes = {}
 
-         self.transform_to_gameobject = {}
 
-         self.gameobject_to_transform = {}
 
-         self.transform_children = {}
 
-         self.stripped_gameobjects = {}
 
-         self.processed_relationships = set()
 
-     def load_prefab_data(self, prefab_guid):
 
-         """Load and parse prefab data from GUID"""
 
-         if not prefab_guid or prefab_guid not in self.guid_map:
 
-             return {}
 
-         
 
-         prefab_path = self.guid_map[prefab_guid]
 
-         try:
 
-             documents = load_unity_yaml(prefab_path)
 
-             if not documents:
 
-                 return {}
 
-             
 
-             raw_object_map = {int(doc.anchor.value): doc for doc in documents if hasattr(doc, 'anchor') and doc.anchor is not None}
 
-             return {file_id: convert_to_plain_python_types(obj) for file_id, obj in raw_object_map.items()}
 
-         except Exception:
 
-             return {}
 
-     def calculate_deep_sibling_index(self, scene_transform_id, prefab_guid):
 
-         """Calculate deep sibling index for scene objects that are children of prefab objects"""
 
-         scene_transform = self.object_map.get(scene_transform_id, {}).get('Transform', {})
 
-         scene_root_order = scene_transform.get('m_RootOrder', 0)
 
-         
 
-         # Get parent transform (should be stripped)
 
-         parent_transform_id = scene_transform.get('m_Father', {}).get('fileID')
 
-         if not parent_transform_id:
 
-             return str(scene_root_order)
 
-         
 
-         parent_transform = self.object_map.get(parent_transform_id, {}).get('Transform', {})
 
-         corresponding_source = parent_transform.get('m_CorrespondingSourceObject', {})
 
-         prefab_transform_id = corresponding_source.get('fileID')
 
-         
 
-         if not prefab_transform_id:
 
-             return str(scene_root_order)
 
-         
 
-         # Load prefab data and traverse hierarchy
 
-         prefab_data = self.load_prefab_data(prefab_guid)
 
-         if not prefab_data:
 
-             return str(scene_root_order)
 
-         
 
-         # Build sibling index by traversing up the prefab hierarchy
 
-         sibling_indices = []
 
-         current_transform_id = prefab_transform_id
 
-         
 
-         while current_transform_id and current_transform_id in prefab_data:
 
-             transform_data = prefab_data.get(current_transform_id, {}).get('Transform', {})
 
-             if not transform_data:
 
-                 break
 
-             
 
-             parent_id = transform_data.get('m_Father', {}).get('fileID')
 
-             
 
-             # Only add the index if this is NOT the root transform of the prefab
 
-             if parent_id and parent_id != 0:
 
-                 root_order = transform_data.get('m_RootOrder', 0)
 
-                 sibling_indices.insert(0, str(root_order))
 
-             
 
-             # Break the loop if there's no parent
 
-             if not parent_id or parent_id == 0:
 
-                 break
 
-             
 
-             current_transform_id = parent_id
 
-         
 
-         # Add the scene object's own root order at the end
 
-         sibling_indices.append(str(scene_root_order))
 
-         return '-'.join(sibling_indices)
 
-     def process_first_pass(self):
 
-         """First pass: Build relationship maps and create basic nodes"""
 
-         stripped_transforms = {}
 
-         stripped_gameobjects = {}
 
-         prefab_instances = {}
 
-         
 
-         for file_id, obj_data in self.object_map.items():
 
-             if 'GameObject' in obj_data:
 
-                 go_info = obj_data['GameObject']
 
-                 
 
-                 # Always create a node for a GameObject.
 
-                 # If it's part of a prefab, we'll link it later.
 
-                 self.nodes[file_id] = {
 
-                     'fileID': str(file_id),
 
-                     'm_Name': go_info.get('m_Name', 'Unknown'),
 
-                     'm_IsActive': go_info.get('m_IsActive', 1),
 
-                     'm_TagString': go_info.get('m_TagString', 'Untagged'),
 
-                     'm_Layer': go_info.get('m_Layer', 0),
 
-                     'components': [],
 
-                     'children': []
 
-                 }
 
-                 
 
-                 # If it's a stripped GameObject, track it for component linking
 
-                 is_stripped = any('stripped' in str(key) for key in obj_data.keys() if hasattr(key, '__str__'))
 
-                 if is_stripped:
 
-                     prefab_instance_id = go_info.get('m_PrefabInstance', {}).get('fileID')
 
-                     if prefab_instance_id:
 
-                         stripped_gameobjects[file_id] = {
 
-                             'prefab_instance_id': prefab_instance_id,
 
-                             'm_CorrespondingSourceObject': go_info.get('m_CorrespondingSourceObject', {})
 
-                         }
 
-                 
 
-                 # If it's the root GameObject of a prefab instance, add a link for merging
 
-                 prefab_instance_id = go_info.get('m_PrefabInstance', {}).get('fileID')
 
-                 if prefab_instance_id and not is_stripped:
 
-                     self.nodes[file_id]['prefab_instance_id'] = prefab_instance_id
 
-                 
 
-             elif 'PrefabInstance' in obj_data:
 
-                 prefab_info = obj_data['PrefabInstance']
 
-                 source_prefab = prefab_info.get('m_SourcePrefab', {})
 
-                 modifications = prefab_info.get('m_Modification', {})
 
-                 
 
-                 # Extract m_IsActive, m_Name, and m_RootOrder from modifications
 
-                 m_is_active = 1  # default
 
-                 m_name = None
 
-                 m_root_order = 999999 # default
 
-                 for mod in modifications.get('m_Modifications', []):
 
-                     if mod.get('propertyPath') == 'm_IsActive':
 
-                         m_is_active = mod.get('value', 1)
 
-                     elif mod.get('propertyPath') == 'm_Name':
 
-                         m_name = mod.get('value')
 
-                     elif mod.get('propertyPath') == 'm_RootOrder':
 
-                         m_root_order = mod.get('value', 999999)
 
-                 prefab_instances[file_id] = {
 
-                     'source_prefab_guid': source_prefab.get('guid'),
 
-                     'm_TransformParent': modifications.get('m_TransformParent', {}).get('fileID'),
 
-                     'm_IsActive': m_is_active,
 
-                     'm_Name': m_name,
 
-                     'm_RootOrder': m_root_order
 
-                 }
 
-                 
 
-             elif 'Transform' in obj_data:
 
-                 transform_info = obj_data['Transform']
 
-                 
 
-                 # Check if this is a stripped transform (has m_PrefabInstance)
 
-                 prefab_instance_id = transform_info.get('m_PrefabInstance', {}).get('fileID')
 
-                 if prefab_instance_id:
 
-                     stripped_transforms[file_id] = {
 
-                         'prefab_instance_id': prefab_instance_id,
 
-                         'm_CorrespondingSourceObject': transform_info.get('m_CorrespondingSourceObject', {})
 
-                     }
 
-                 
 
-                 # Build transform mappings
 
-                 gameobject_id = transform_info.get('m_GameObject', {}).get('fileID')
 
-                 if gameobject_id:
 
-                     self.transform_to_gameobject[file_id] = gameobject_id
 
-                     self.gameobject_to_transform[gameobject_id] = file_id
 
-                 
 
-                 # Build parent-child relationships
 
-                 parent_id = transform_info.get('m_Father', {}).get('fileID')
 
-                 if parent_id and parent_id != 0:
 
-                     if parent_id not in self.transform_children:
 
-                         self.transform_children[parent_id] = []
 
-                     self.transform_children[parent_id].append(file_id)
 
-         
 
-         # Create prefab nodes
 
-         for prefab_id, prefab_info in prefab_instances.items():
 
-             self.prefab_nodes[prefab_id] = {
 
-                 'fileID': str(prefab_id),
 
-                 'source_prefab_guid': prefab_info['source_prefab_guid'],
 
-                 'm_IsActive': prefab_info['m_IsActive'],
 
-                 'm_RootOrder': prefab_info.get('m_RootOrder', 999999),
 
-                 'components': [],  # Will be populated with override components
 
-                 'children': []
 
-             }
 
-             if prefab_info.get('m_Name'):
 
-                 self.prefab_nodes[prefab_id]['m_Name'] = prefab_info['m_Name']
 
-         
 
-         # Store for second pass
 
-         self.stripped_transforms = stripped_transforms
 
-         self.stripped_gameobjects = stripped_gameobjects
 
-         self.prefab_instances = prefab_instances
 
-     def process_second_pass(self):
 
-         """Second pass: Build hierarchy relationships"""
 
-         
 
-         # 1. Handle standard GameObject parent-child relationships
 
-         for go_id, node in self.nodes.items():
 
-             transform_id = self.gameobject_to_transform.get(go_id)
 
-             if not transform_id:
 
-                 continue
 
-             
 
-             # Get child transforms
 
-             child_transform_ids = self.transform_children.get(transform_id, [])
 
-             for child_transform_id in child_transform_ids:
 
-                 
 
-                 # Check if child transform is stripped (part of prefab)
 
-                 if child_transform_id in self.stripped_transforms:
 
-                     stripped_info = self.stripped_transforms[child_transform_id]
 
-                     prefab_instance_id = stripped_info['prefab_instance_id']
 
-                     
 
-                     if prefab_instance_id in self.prefab_nodes:
 
-                         relationship_key = f"{go_id}->{prefab_instance_id}"
 
-                         if relationship_key not in self.processed_relationships:
 
-                             node['children'].append(self.prefab_nodes[prefab_instance_id])
 
-                             self.processed_relationships.add(relationship_key)
 
-                 else:
 
-                     # Regular GameObject child
 
-                     child_go_id = self.transform_to_gameobject.get(child_transform_id)
 
-                     if child_go_id and child_go_id in self.nodes:
 
-                         relationship_key = f"{go_id}->{child_go_id}"
 
-                         if relationship_key not in self.processed_relationships:
 
-                             node['children'].append(self.nodes[child_go_id])
 
-                             self.processed_relationships.add(relationship_key)
 
-         
 
-         # 2. Handle prefab-to-parent relationships
 
-         for prefab_id, prefab_info in self.prefab_instances.items():
 
-             parent_transform_id = prefab_info['m_TransformParent']
 
-             if not parent_transform_id or parent_transform_id == 0:
 
-                 continue  # Root prefab, will be handled in get_root_objects
 
-             
 
-             # Find parent GameObject or parent prefab
 
-             parent_go_id = self.transform_to_gameobject.get(parent_transform_id)
 
-             if parent_go_id and parent_go_id in self.nodes:
 
-                 # Prefab child of GameObject
 
-                 relationship_key = f"{parent_go_id}->{prefab_id}"
 
-                 if relationship_key not in self.processed_relationships:
 
-                     self.nodes[parent_go_id]['children'].append(self.prefab_nodes[prefab_id])
 
-                     self.processed_relationships.add(relationship_key)
 
-             elif parent_transform_id in self.stripped_transforms:
 
-                 # Prefab child of another prefab
 
-                 stripped_info = self.stripped_transforms[parent_transform_id]
 
-                 parent_prefab_id = stripped_info['prefab_instance_id']
 
-                 
 
-                 if parent_prefab_id in self.prefab_nodes:
 
-                     relationship_key = f"{parent_prefab_id}->{prefab_id}"
 
-                     if relationship_key not in self.processed_relationships:
 
-                         self.prefab_nodes[parent_prefab_id]['children'].append(self.prefab_nodes[prefab_id])
 
-                         self.processed_relationships.add(relationship_key)
 
-         
 
-         # 3. Handle scene objects as children of prefabs (with deep sibling index)
 
-         for go_id, node in self.nodes.items():
 
-             transform_id = self.gameobject_to_transform.get(go_id)
 
-             if not transform_id:
 
-                 continue
 
-             
 
-             # Find parent transform
 
-             parent_transform_id = None
 
-             for parent_id, children in self.transform_children.items():
 
-                 if transform_id in children:
 
-                     parent_transform_id = parent_id
 
-                     break
 
-             
 
-             if parent_transform_id and parent_transform_id in self.stripped_transforms:
 
-                 stripped_info = self.stripped_transforms[parent_transform_id]
 
-                 prefab_instance_id = stripped_info['prefab_instance_id']
 
-                 
 
-                 if prefab_instance_id in self.prefab_nodes:
 
-                     # Calculate deep sibling index
 
-                     prefab_guid = self.prefab_instances[prefab_instance_id]['source_prefab_guid']
 
-                     deep_sibling_index = self.calculate_deep_sibling_index(transform_id, prefab_guid)
 
-                     if deep_sibling_index:
 
-                         node['deep_sibling_index'] = deep_sibling_index
 
-                     
 
-                     # Add as child of prefab
 
-                     relationship_key = f"{prefab_instance_id}->{go_id}"
 
-                     if relationship_key not in self.processed_relationships:
 
-                         self.prefab_nodes[prefab_instance_id]['children'].append(node)
 
-                         self.processed_relationships.add(relationship_key)
 
-     def process_third_pass(self):
 
-         """Third pass: Extract components for both GameObjects and PrefabInstances"""
 
-         for file_id, obj_data in self.object_map.items():
 
-             # Skip GameObjects, Transforms, and PrefabInstances
 
-             if any(key in obj_data for key in ['GameObject', 'Transform', 'PrefabInstance']):
 
-                 continue
 
-             
 
-             # Find the GameObject this component belongs to
 
-             gameobject_id = None
 
-             for key, data in obj_data.items():
 
-                 if isinstance(data, dict) and 'm_GameObject' in data:
 
-                     gameobject_id = data['m_GameObject'].get('fileID')
 
-                     break
 
-             
 
-             if not gameobject_id:
 
-                 continue
 
-             
 
-             component_name = next((key for key in obj_data if key not in ['m_ObjectHideFlags']), 'Unknown')
 
-             
 
-             # Handle MonoBehaviour with script GUID
 
-             if component_name == 'MonoBehaviour':
 
-                 script_info = obj_data['MonoBehaviour'].get('m_Script', {})
 
-                 script_guid = script_info.get('guid')
 
-                 if script_guid:
 
-                     component_entry = script_guid
 
-                 else:
 
-                     component_entry = "MonoBehaviour"
 
-             else:
 
-                 component_entry = component_name
 
-             
 
-             # Check if this component belongs to a regular GameObject
 
-             if gameobject_id in self.nodes:
 
-                 if component_entry not in self.nodes[gameobject_id]['components']:
 
-                     self.nodes[gameobject_id]['components'].append(component_entry)
 
-             
 
-             # Check if this component belongs to a stripped GameObject (prefab override)
 
-             elif gameobject_id in self.stripped_gameobjects:
 
-                 stripped_info = self.stripped_gameobjects[gameobject_id]
 
-                 prefab_instance_id = stripped_info['prefab_instance_id']
 
-                 
 
-                 if prefab_instance_id in self.prefab_nodes:
 
-                     if component_entry not in self.prefab_nodes[prefab_instance_id]['components']:
 
-                         self.prefab_nodes[prefab_instance_id]['components'].append(component_entry)
 
-             
 
-             # If neither, this component belongs to an unknown GameObject (potential orphan)
 
-             else:
 
-                 print(f"Warning: Component {component_name} (ID: {file_id}) references unknown GameObject {gameobject_id}")
 
-     def verification_pass(self):
 
-         """
 
-         Verifies and fixes parent-child relationships that might have been missed
 
-         by using a direct child->parent lookup. This acts as a patch for the
 
-         less reliable reverse-lookup used in process_second_pass.
 
-         """
 
-         for go_id, node in list(self.nodes.items()):
 
-             transform_id = self.gameobject_to_transform.get(go_id)
 
-             if not transform_id:
 
-                 continue
 
-             transform_info = self.object_map.get(transform_id, {}).get('Transform', {})
 
-             parent_transform_id = transform_info.get('m_Father', {}).get('fileID')
 
-             if not parent_transform_id or parent_transform_id == 0:
 
-                 continue  # This is a root object, no parent to verify.
 
-             # Case 1: Parent is a PrefabInstance (via a stripped transform)
 
-             if parent_transform_id in self.stripped_transforms:
 
-                 stripped_info = self.stripped_transforms[parent_transform_id]
 
-                 parent_prefab_id = stripped_info['prefab_instance_id']
 
-                 if parent_prefab_id in self.prefab_nodes:
 
-                     relationship_key = f"{parent_prefab_id}->{go_id}"
 
-                     if relationship_key not in self.processed_relationships:
 
-                         # Add deep sibling index if it's a child of a prefab
 
-                         prefab_guid = self.prefab_instances[parent_prefab_id]['source_prefab_guid']
 
-                         deep_sibling_index = self.calculate_deep_sibling_index(transform_id, prefab_guid)
 
-                         if deep_sibling_index:
 
-                             node['deep_sibling_index'] = deep_sibling_index
 
-                         
 
-                         self.prefab_nodes[parent_prefab_id]['children'].append(node)
 
-                         self.processed_relationships.add(relationship_key)
 
-             # Case 2: Parent is a regular GameObject
 
-             else:
 
-                 parent_go_id = self.transform_to_gameobject.get(parent_transform_id)
 
-                 if parent_go_id and parent_go_id in self.nodes:
 
-                     relationship_key = f"{parent_go_id}->{go_id}"
 
-                     if relationship_key not in self.processed_relationships:
 
-                         self.nodes[parent_go_id]['children'].append(node)
 
-                         self.processed_relationships.add(relationship_key)
 
-     def merge_prefab_data_pass(self):
 
-         """Fourth pass: Merge GameObject data into their corresponding PrefabInstance nodes"""
 
-         nodes_to_delete = []
 
-         for go_id, go_node in self.nodes.items():
 
-             prefab_instance_id = go_node.get('prefab_instance_id')
 
-             if not prefab_instance_id:
 
-                 continue
 
-             if prefab_instance_id in self.prefab_nodes:
 
-                 # Merge the GameObject data into the PrefabInstance node
 
-                 # The prefab node's existing data takes precedence
 
-                 prefab_node = self.prefab_nodes[prefab_instance_id]
 
-                 
 
-                 # Create a copy of the gameobject node to avoid modifying during iteration
 
-                 go_node_copy = go_node.copy()
 
-                 
 
-                 # Remove keys that should not be merged or are already handled
 
-                 del go_node_copy['fileID']
 
-                 del go_node_copy['prefab_instance_id']
 
-                 
 
-                 # Update prefab_node with go_node_copy data
 
-                 # This will overwrite keys in prefab_node with values from go_node_copy
 
-                 # if they exist in both. We want the opposite.
 
-                 
 
-                 # Let's do it manually to ensure prefab data is kept
 
-                 for key, value in go_node_copy.items():
 
-                     if key not in prefab_node:
 
-                         prefab_node[key] = value
 
-                 
 
-                 # Specifically handle children and components to merge them
 
-                 prefab_node['children'].extend(go_node_copy.get('children', []))
 
-                 
 
-                 # Merge components, avoiding duplicates
 
-                 existing_components = set(str(c) for c in prefab_node.get('components', []))
 
-                 for comp in go_node_copy.get('components', []):
 
-                     if str(comp) not in existing_components:
 
-                         prefab_node['components'].append(comp)
 
-                 
 
-                 # Rename 'components' to 'addedComponents' for clarity
 
-                 if 'components' in prefab_node:
 
-                     prefab_node['addedComponents'] = prefab_node.pop('components')
 
-                 # Mark the original GameObject node for deletion
 
-                 nodes_to_delete.append(go_id)
 
-         # Remove the now-redundant GameObject nodes
 
-         for go_id in nodes_to_delete:
 
-             del self.nodes[go_id]
 
-     def cleanup_pass(self, nodes):
 
-         """
 
-         Recursively cleans up temporary or internal properties from the final node structure.
 
-         """
 
-         # List of properties to remove from the final output
 
-         cleanup_keys = ['m_RootOrder']
 
-         for node in nodes:
 
-             for key in cleanup_keys:
 
-                 if key in node:
 
-                     del node[key]
 
-             
 
-             if 'children' in node and node['children']:
 
-                 self.cleanup_pass(node['children'])
 
-     def load_documents(self, file_path):
 
-         """Loads and parses the yaml documents from a scene/prefab file."""
 
-         documents = load_unity_yaml(file_path)
 
-         if not documents:
 
-             self.object_map = {}
 
-             return False
 
-         
 
-         raw_object_map = {int(doc.anchor.value): doc for doc in documents if hasattr(doc, 'anchor') and doc.anchor is not None}
 
-         self.object_map = {file_id: convert_to_plain_python_types(obj) for file_id, obj in raw_object_map.items()}
 
-         return True
 
-     def get_hierarchy(self):
 
-         """Builds and returns the final, cleaned-up hierarchy from the processed data."""
 
-         # Use the centralized parser to get the final, sorted root objects
 
-         parser = HierarchyParser(self.object_map)
 
-         root_object_ids = parser.get_root_object_ids()
 
-         # Build the final list of nodes from the identified roots
 
-         root_nodes = []
 
-         all_nodes = {**self.nodes, **self.prefab_nodes}
 
-         for file_id, _ in root_object_ids:
 
-             if file_id in all_nodes:
 
-                 root_nodes.append(all_nodes[file_id])
 
-         
 
-         # Run the final cleanup pass
 
-         self.cleanup_pass(root_nodes)
 
-         return root_nodes
 
-     def process_file(self, file_path):
 
-         """Main processing method"""
 
-         if not self.load_documents(file_path):
 
-             return []
 
-         # Process in passes
 
-         self.process_first_pass()
 
-         self.process_second_pass()
 
-         self.verification_pass()
 
-         self.process_third_pass()
 
-         self.merge_prefab_data_pass()
 
-         return self.get_hierarchy()
 
- if __name__ == "__main__":
 
-     # This script is intended to be used as a module.
 
-     # For direct execution, see extract_mid_level.py.
 
-     pass
 
 
  |