| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 | 
							- import sys
 
- 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 TRSSceneProcessor:
 
-     """
 
-     A specialized processor that uses the robust multi-pass system from the mid-level
 
-     extractor to build a correct scene hierarchy, then extracts only TRS data.
 
-     """
 
-     def __init__(self, guid_map):
 
-         self.guid_map = guid_map
 
-         self.object_map = {}
 
-         self.prefab_cache = {}
 
-         self.nodes = {}
 
-         self.prefab_nodes = {}
 
-         self.transform_to_gameobject = {}
 
-         self.gameobject_to_transform = {}
 
-         self.transform_children = {}
 
-         self.stripped_gameobjects = {}
 
-         self.stripped_transforms = {}
 
-         self.prefab_instances = {}
 
-         self.processed_relationships = set()
 
-     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 _load_prefab_data(self, prefab_guid):
 
-         """Load and cache prefab data from its GUID."""
 
-         if prefab_guid in self.prefab_cache:
 
-             return self.prefab_cache[prefab_guid]
 
-         
 
-         if not prefab_guid or prefab_guid not in self.guid_map:
 
-             self.prefab_cache[prefab_guid] = None
 
-             return None
 
-         prefab_path = self.guid_map[prefab_guid]
 
-         try:
 
-             documents = load_unity_yaml(prefab_path)
 
-             if not documents:
 
-                 self.prefab_cache[prefab_guid] = None
 
-                 return None
 
-             
 
-             raw_object_map = {int(doc.anchor.value): doc for doc in documents if hasattr(doc, 'anchor') and doc.anchor is not None}
 
-             prefab_data = {file_id: convert_to_plain_python_types(obj) for file_id, obj in raw_object_map.items()}
 
-             self.prefab_cache[prefab_guid] = prefab_data
 
-             return prefab_data
 
-         except Exception:
 
-             self.prefab_cache[prefab_guid] = None
 
-             return None
 
-     def build_relationship_maps(self):
 
-         """Pass 1: Build all necessary maps for hierarchy construction."""
 
-         for file_id, obj_data in self.object_map.items():
 
-             if 'GameObject' in obj_data:
 
-                 go_info = obj_data['GameObject']
 
-                 self.nodes[file_id] = {'fileID': str(file_id), 'm_Name': go_info.get('m_Name', 'Unknown'), 'children': []}
 
-                 if any('stripped' in str(k) for k in obj_data.keys()):
 
-                     self.stripped_gameobjects[file_id] = go_info
 
-                 prefab_instance_id = go_info.get('m_PrefabInstance', {}).get('fileID')
 
-                 if prefab_instance_id and not file_id in self.stripped_gameobjects:
 
-                     self.nodes[file_id]['prefab_instance_id'] = prefab_instance_id
 
-             elif 'PrefabInstance' in obj_data:
 
-                 prefab_info = obj_data['PrefabInstance']
 
-                 self.prefab_instances[file_id] = prefab_info
 
-                 
 
-                 # Get name from modifications or fallback to prefab asset filename
 
-                 mod_name = next((mod['value'] for mod in prefab_info.get('m_Modification', {}).get('m_Modifications', []) if mod.get('propertyPath') == 'm_Name'), None)
 
-                 
 
-                 if not mod_name:
 
-                     guid = prefab_info.get('m_SourcePrefab', {}).get('guid')
 
-                     if guid and guid in self.guid_map:
 
-                         prefab_path = Path(self.guid_map[guid])
 
-                         mod_name = prefab_path.stem
 
-                     else:
 
-                         mod_name = 'Unknown'
 
-                 self.prefab_nodes[file_id] = {'fileID': str(file_id), 'm_Name': mod_name, 'children': []}
 
-             elif 'Transform' in obj_data:
 
-                 transform_info = obj_data['Transform']
 
-                 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
 
-                 
 
-                 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)
 
-                 
 
-                 if transform_info.get('m_PrefabInstance', {}).get('fileID'):
 
-                     self.stripped_transforms[file_id] = transform_info
 
-     def build_hierarchy(self):
 
-         """Pass 2: Connect nodes into a hierarchy."""
 
-         # Link standard GameObjects
 
-         for go_id, node in self.nodes.items():
 
-             transform_id = self.gameobject_to_transform.get(go_id)
 
-             if not transform_id: continue
 
-             
 
-             child_transform_ids = self.transform_children.get(transform_id, [])
 
-             for child_transform_id in child_transform_ids:
 
-                 child_go_id = self.transform_to_gameobject.get(child_transform_id)
 
-                 if child_go_id and child_go_id in self.nodes:
 
-                     node['children'].append(self.nodes[child_go_id])
 
-         # Link prefab instances to their parents
 
-         for prefab_id, prefab_info in self.prefab_instances.items():
 
-             parent_transform_id = prefab_info.get('m_Modification', {}).get('m_TransformParent', {}).get('fileID')
 
-             if not parent_transform_id: continue
 
-             parent_go_id = self.transform_to_gameobject.get(parent_transform_id)
 
-             if parent_go_id and parent_go_id in self.nodes:
 
-                 self.nodes[parent_go_id]['children'].append(self.prefab_nodes[prefab_id])
 
-             elif parent_transform_id in self.stripped_transforms:
 
-                 parent_prefab_instance_id = self.stripped_transforms[parent_transform_id].get('m_PrefabInstance', {}).get('fileID')
 
-                 if parent_prefab_instance_id in self.prefab_nodes:
 
-                     self.prefab_nodes[parent_prefab_instance_id]['children'].append(self.prefab_nodes[prefab_id])
 
-         # Link scene objects that are children of prefabs
 
-         for go_id, node in 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 parent_transform_id in self.stripped_transforms:
 
-                 parent_prefab_instance_id = self.stripped_transforms[parent_transform_id].get('m_PrefabInstance', {}).get('fileID')
 
-                 if parent_prefab_instance_id in self.prefab_nodes:
 
-                     self.prefab_nodes[parent_prefab_instance_id]['children'].append(node)
 
-     def merge_prefab_info(self):
 
-         """Pass 3: Merge root GameObject data into PrefabInstance nodes."""
 
-         nodes_to_delete = []
 
-         for go_id, go_node in self.nodes.items():
 
-             prefab_instance_id = go_node.get('prefab_instance_id')
 
-             if prefab_instance_id and prefab_instance_id in self.prefab_nodes:
 
-                 prefab_node = self.prefab_nodes[prefab_instance_id]
 
-                 prefab_info = self.prefab_instances[prefab_instance_id]
 
-                 
 
-                 # Name is now set in build_relationship_maps. We just merge the rest.
 
-                 prefab_node['source_prefab_guid'] = prefab_info.get('m_SourcePrefab', {}).get('guid')
 
-                 prefab_node['children'].extend(go_node.get('children', []))
 
-                 nodes_to_delete.append(go_id)
 
-         for go_id in nodes_to_delete:
 
-             del self.nodes[go_id]
 
-     def extract_trs_data(self, nodes):
 
-         """Pass 4: Recursively find and attach TRS data to each node."""
 
-         for node in nodes:
 
-             file_id = int(node['fileID'])
 
-             
 
-             # Default TRS values
 
-             trs = {"position": {"x": 0, "y": 0, "z": 0}, "rotation": {"x": 0, "y": 0, "z": 0, "w": 1}, "scale": {"x": 1, "y": 1, "z": 1}}
 
-             if 'source_prefab_guid' in node: # It's a prefab instance
 
-                 prefab_instance_info = self.prefab_instances.get(file_id, {})
 
-                 modifications = prefab_instance_info.get('m_Modification', {}).get('m_Modifications', [])
 
-                 
 
-                 # Find the root transform of the prefab to get base TRS
 
-                 prefab_data = self._load_prefab_data(node['source_prefab_guid'])
 
-                 if prefab_data:
 
-                     for obj in prefab_data.values():
 
-                         if 'Transform' in obj and obj['Transform'].get('m_Father', {}).get('fileID') == 0:
 
-                             base_transform = obj['Transform']
 
-                             trs['position'] = base_transform.get('m_LocalPosition', trs['position'])
 
-                             trs['rotation'] = base_transform.get('m_LocalRotation', trs['rotation'])
 
-                             trs['scale'] = base_transform.get('m_LocalScale', trs['scale'])
 
-                             break
 
-                 
 
-                 # Apply modifications
 
-                 for mod in modifications:
 
-                     prop = mod.get('propertyPath', '')
 
-                     # Skip any property that is not part of a Transform
 
-                     if not ('m_LocalPosition' in prop or 'm_LocalRotation' in prop or 'm_LocalScale' in prop):
 
-                         continue
 
-                     
 
-                     try:
 
-                         val = float(mod.get('value', 0))
 
-                     except (ValueError, TypeError):
 
-                         # If the value is not a valid float, skip this modification
 
-                         print(f"Warning: Could not convert value '{mod.get('value')}' for property '{prop}' to float. Skipping.", file=sys.stderr)
 
-                         continue
 
-                     if prop == 'm_LocalPosition.x': trs['position']['x'] = val
 
-                     elif prop == 'm_LocalPosition.y': trs['position']['y'] = val
 
-                     elif prop == 'm_LocalPosition.z': trs['position']['z'] = val
 
-                     elif prop == 'm_LocalRotation.x': trs['rotation']['x'] = val
 
-                     elif prop == 'm_LocalRotation.y': trs['rotation']['y'] = val
 
-                     elif prop == 'm_LocalRotation.z': trs['rotation']['z'] = val
 
-                     elif prop == 'm_LocalRotation.w': trs['rotation']['w'] = val
 
-                     elif prop == 'm_LocalScale.x': trs['scale']['x'] = val
 
-                     elif prop == 'm_LocalScale.y': trs['scale']['y'] = val
 
-                     elif prop == 'm_LocalScale.z': trs['scale']['z'] = val
 
-             else: # It's a regular GameObject
 
-                 transform_id = self.gameobject_to_transform.get(file_id)
 
-                 if transform_id and transform_id in self.object_map:
 
-                     transform_info = self.object_map[transform_id].get('Transform', {})
 
-                     trs['position'] = transform_info.get('m_LocalPosition', trs['position'])
 
-                     trs['rotation'] = transform_info.get('m_LocalRotation', trs['rotation'])
 
-                     trs['scale'] = transform_info.get('m_LocalScale', trs['scale'])
 
-             
 
-             node['trs'] = trs
 
-             
 
-             if 'children' in node and node['children']:
 
-                 self.extract_trs_data(node['children'])
 
-     def cleanup_hierarchy(self, nodes):
 
-         """Pass 5: Remove intermediate data, leaving only the desired fields."""
 
-         final_nodes = []
 
-         for node in nodes:
 
-             clean_node = {
 
-                 'name': node.get('m_Name', 'Unknown'),
 
-                 'trs': node.get('trs', {}),
 
-                 'children': self.cleanup_hierarchy(node.get('children', []))
 
-             }
 
-             if 'source_prefab_guid' in node:
 
-                 clean_node['source_prefab_guid'] = node['source_prefab_guid']
 
-             final_nodes.append(clean_node)
 
-         return final_nodes
 
-     def process(self):
 
-         """Main processing pipeline."""
 
-         if not self.object_map:
 
-             return []
 
-         # --- Run all passes ---
 
-         self.build_relationship_maps()
 
-         self.build_hierarchy()
 
-         self.merge_prefab_info()
 
-         
 
-         # Get root nodes for the final passes
 
-         parser = HierarchyParser(self.object_map)
 
-         root_object_ids = parser.get_root_object_ids()
 
-         
 
-         all_nodes = {**self.nodes, **self.prefab_nodes}
 
-         root_nodes = [all_nodes[file_id] for file_id, _ in root_object_ids if file_id in all_nodes]
 
-         self.extract_trs_data(root_nodes)
 
-         final_hierarchy = self.cleanup_hierarchy(root_nodes)
 
-         return final_hierarchy
 
 
  |