|
@@ -8,6 +8,7 @@ utils_path = Path(__file__).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):
|
|
@@ -411,105 +412,6 @@ class UnitySceneProcessor:
|
|
|
for go_id in nodes_to_delete:
|
|
|
del self.nodes[go_id]
|
|
|
|
|
|
- def get_root_objects(self):
|
|
|
- """Get all root-level objects, sorted by m_RootOrder, and handle orphans"""
|
|
|
- root_objects = []
|
|
|
- all_children_ids = set()
|
|
|
-
|
|
|
- # Collect all child IDs to identify orphans
|
|
|
- for go_id, node in self.nodes.items():
|
|
|
- self._collect_child_ids(node, all_children_ids)
|
|
|
- for prefab_id, prefab_node in self.prefab_nodes.items():
|
|
|
- self._collect_child_ids(prefab_node, all_children_ids)
|
|
|
-
|
|
|
- # Find GameObjects that have no parent and collect their m_RootOrder
|
|
|
- gameobject_roots = []
|
|
|
- for go_id, node in self.nodes.items():
|
|
|
- transform_id = self.gameobject_to_transform.get(go_id)
|
|
|
- if not transform_id:
|
|
|
- # No transform - likely orphan
|
|
|
- if go_id not in all_children_ids:
|
|
|
- node['is_orphan'] = True
|
|
|
- gameobject_roots.append((node, 999999)) # Orphans go to end
|
|
|
- continue
|
|
|
-
|
|
|
- # Check if this transform has a parent
|
|
|
- has_parent = False
|
|
|
- for parent_id, children in self.transform_children.items():
|
|
|
- if transform_id in children:
|
|
|
- has_parent = True
|
|
|
- break
|
|
|
-
|
|
|
- # Also check if it's a child of any prefab
|
|
|
- is_prefab_child = False
|
|
|
- for parent_transform_id in self.stripped_transforms:
|
|
|
- if transform_id in self.transform_children.get(parent_transform_id, []):
|
|
|
- is_prefab_child = True
|
|
|
- break
|
|
|
-
|
|
|
- if not has_parent and not is_prefab_child:
|
|
|
- if go_id not in all_children_ids:
|
|
|
- # Get m_RootOrder from transform
|
|
|
- root_order = self._get_root_order(transform_id)
|
|
|
- gameobject_roots.append((node, root_order))
|
|
|
- else:
|
|
|
- # This is an orphan that was somehow referenced but not properly parented
|
|
|
- node['is_orphan'] = True
|
|
|
- gameobject_roots.append((node, 999999)) # Orphans go to end
|
|
|
-
|
|
|
- # Find root prefab instances and collect their m_RootOrder
|
|
|
- prefab_roots = []
|
|
|
- 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:
|
|
|
- if prefab_id in self.prefab_nodes:
|
|
|
- if prefab_id not in all_children_ids:
|
|
|
- # Get m_RootOrder directly from the prefab node
|
|
|
- root_order = self.prefab_nodes[prefab_id].get('m_RootOrder', 999999)
|
|
|
- prefab_roots.append((self.prefab_nodes[prefab_id], root_order))
|
|
|
- else:
|
|
|
- # Orphan prefab
|
|
|
- self.prefab_nodes[prefab_id]['is_orphan'] = True
|
|
|
- prefab_roots.append((self.prefab_nodes[prefab_id], 999999)) # Orphans go to end
|
|
|
-
|
|
|
- # Check for completely disconnected GameObjects (orphans)
|
|
|
- for go_id, node in self.nodes.items():
|
|
|
- if go_id not in all_children_ids and not any(obj[0].get('fileID') == str(go_id) for obj in gameobject_roots):
|
|
|
- node['is_orphan'] = True
|
|
|
- gameobject_roots.append((node, 999999)) # Orphans go to end
|
|
|
-
|
|
|
- # Check for completely disconnected PrefabInstances (orphans)
|
|
|
- for prefab_id, prefab_node in self.prefab_nodes.items():
|
|
|
- if prefab_id not in all_children_ids and not any(obj[0].get('fileID') == str(prefab_id) for obj in prefab_roots):
|
|
|
- prefab_node['is_orphan'] = True
|
|
|
- prefab_roots.append((prefab_node, 999999)) # Orphans go to end
|
|
|
-
|
|
|
- # Combine and sort all root objects by m_RootOrder
|
|
|
- all_roots = gameobject_roots + prefab_roots
|
|
|
- all_roots.sort(key=lambda x: x[1]) # Sort by m_RootOrder (second element of tuple)
|
|
|
-
|
|
|
- return [obj[0] for obj in all_roots] # Return only the objects, not the tuples
|
|
|
-
|
|
|
- def _get_root_order(self, transform_id):
|
|
|
- """Get m_RootOrder from a transform"""
|
|
|
- if transform_id not in self.object_map:
|
|
|
- return 999999 # Default for missing transforms
|
|
|
-
|
|
|
- transform_data = self.object_map[transform_id].get('Transform', {})
|
|
|
- return transform_data.get('m_RootOrder', 999999)
|
|
|
-
|
|
|
- def _collect_child_ids(self, node, child_ids_set):
|
|
|
- """Recursively collect all child IDs from a node tree"""
|
|
|
- for child in node.get('children', []):
|
|
|
- child_id = child.get('fileID')
|
|
|
- if child_id:
|
|
|
- # Convert to int for comparison with our keys
|
|
|
- try:
|
|
|
- child_ids_set.add(int(child_id))
|
|
|
- except ValueError:
|
|
|
- pass
|
|
|
- self._collect_child_ids(child, child_ids_set)
|
|
|
-
|
|
|
def cleanup_pass(self, nodes):
|
|
|
"""
|
|
|
Recursively cleans up temporary or internal properties from the final node structure.
|
|
@@ -543,61 +445,24 @@ class UnitySceneProcessor:
|
|
|
self.process_third_pass()
|
|
|
self.merge_prefab_data_pass()
|
|
|
|
|
|
- # Get the final, sorted root objects
|
|
|
- root_objects = self.get_root_objects()
|
|
|
+ # 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_objects)
|
|
|
-
|
|
|
- return root_objects
|
|
|
-
|
|
|
+ self.cleanup_pass(root_nodes)
|
|
|
|
|
|
-def main():
|
|
|
- parser = argparse.ArgumentParser(description="Process Unity scene/prefab files into JSON representation")
|
|
|
- parser.add_argument("--input", required=True, help="Path to input .unity or .prefab file")
|
|
|
- parser.add_argument("--guid-map", required=True, help="Path to guid_map.json file")
|
|
|
- parser.add_argument("--output", required=True, help="Path to output .json file")
|
|
|
-
|
|
|
- args = parser.parse_args()
|
|
|
-
|
|
|
- input_path = Path(args.input)
|
|
|
- guid_map_path = Path(args.guid_map)
|
|
|
- output_path = Path(args.output)
|
|
|
-
|
|
|
- if not input_path.exists():
|
|
|
- print(f"Error: Input file {input_path} does not exist", file=sys.stderr)
|
|
|
- sys.exit(1)
|
|
|
-
|
|
|
- if not guid_map_path.exists():
|
|
|
- print(f"Error: GUID map file {guid_map_path} does not exist", file=sys.stderr)
|
|
|
- sys.exit(1)
|
|
|
-
|
|
|
- # Load GUID map
|
|
|
- try:
|
|
|
- with open(guid_map_path, 'r', encoding='utf-8') as f:
|
|
|
- guid_map = json.load(f)
|
|
|
- except Exception as e:
|
|
|
- print(f"Error loading GUID map: {e}", file=sys.stderr)
|
|
|
- sys.exit(1)
|
|
|
-
|
|
|
- # Process the file
|
|
|
- processor = UnitySceneProcessor(guid_map)
|
|
|
- try:
|
|
|
- result = processor.process_file(input_path)
|
|
|
-
|
|
|
- # Ensure output directory exists
|
|
|
- output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
-
|
|
|
- # Write output
|
|
|
- with open(output_path, 'w', encoding='utf-8') as f:
|
|
|
- json.dump(result, f, indent=2, ensure_ascii=False)
|
|
|
-
|
|
|
- print(f"Successfully processed {input_path.name} -> {output_path}")
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- print(f"Error processing file {input_path}: {e}", file=sys.stderr)
|
|
|
- sys.exit(1)
|
|
|
+ return root_nodes
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
- main()
|
|
|
+ # This script is intended to be used as a module.
|
|
|
+ # For direct execution, see extract_mid_level.py.
|
|
|
+ pass
|