from pathlib import Path class HierarchyParser: """ Parses a Unity scene's object_map to identify parent-child relationships and determine the root objects in the hierarchy. """ def __init__(self, object_map): self.object_map = object_map self.transform_to_gameobject = {} self.gameobject_to_transform = {} self.transform_children = {} self.prefab_instances = {} self.stripped_transforms = {} self.all_child_ids = set() self._build_maps() self._find_all_children() def _build_maps(self): """ First pass over the object map to build essential lookups for transforms, GameObjects, and PrefabInstances. """ for file_id, obj_data in self.object_map.items(): if '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) prefab_instance_id = transform_info.get('m_PrefabInstance', {}).get('fileID') if prefab_instance_id: self.stripped_transforms[file_id] = prefab_instance_id elif 'PrefabInstance' in obj_data: prefab_info = obj_data['PrefabInstance'] modifications = prefab_info.get('m_Modification', {}) parent_id = modifications.get('m_TransformParent', {}).get('fileID') if parent_id and parent_id != 0: self.prefab_instances[file_id] = {'parent': parent_id} def _find_all_children(self): """ Iterates through the built maps to populate a set of all fileIDs that are children of another object. """ # Children of regular GameObjects (via Transform) for parent_id, children in self.transform_children.items(): for child_transform_id in children: child_go_id = self.transform_to_gameobject.get(child_transform_id) if child_go_id: self.all_child_ids.add(child_go_id) # Also add stripped transforms that point to prefabs elif child_transform_id in self.stripped_transforms: prefab_instance_id = self.stripped_transforms[child_transform_id] self.all_child_ids.add(prefab_instance_id) # Children that are PrefabInstances for prefab_id, prefab_data in self.prefab_instances.items(): self.all_child_ids.add(prefab_id) def get_root_object_ids(self): """ Identifies all root objects (GameObjects and PrefabInstances) and returns them as a sorted list of tuples containing (fileID, m_RootOrder). """ root_objects = [] # Find root GameObjects for go_id, transform_id in self.gameobject_to_transform.items(): if go_id not in self.all_child_ids: transform_info = self.object_map.get(transform_id, {}).get('Transform', {}) root_order = transform_info.get('m_RootOrder', 999999) root_objects.append((go_id, root_order)) # Find root PrefabInstances for prefab_id, obj_data in self.object_map.items(): if 'PrefabInstance' not in obj_data: continue if prefab_id not in self.all_child_ids: modifications = obj_data['PrefabInstance'].get('m_Modification', {}) root_order = 999999 for mod in modifications.get('m_Modifications', []): if mod.get('propertyPath') == 'm_RootOrder': root_order = mod.get('value', 999999) break root_objects.append((prefab_id, root_order)) # Sort all root objects by their m_RootOrder root_objects.sort(key=lambda x: x[1]) return root_objects