Editor/Output/ArchBuiltPieces.cs

Editor utility that scans a scene and builds a tree of constructed architecture pieces. It finds root GameObjects, walks their children, groups built pieces by owner id, records piece name, path, owner, the GameObject, whether it has a mesh, and nested children.

ReflectionFile Access
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// One named piece as it actually came out - the object it became, whether it renders anything of its own, and
// the pieces nested inside it.
public sealed class ArchBuiltPiece
{
	public string Name { get; init; }
	// The path under its layer: "Gutters", "Gutters/Fascia".
	public string Piece { get; init; }
	public int Owner { get; init; }
	public GameObject Object { get; init; }
	public bool Renders { get; init; }
	public List<ArchBuiltPiece> Children { get; } = new();

	public string Key => ArchParts.Key( Owner, Piece );
}

// WHAT EACH LAYER BUILT, as a tree - read off the scene rather than declared per kind, because a table of
// "a roof has a fascia" is a second opinion the generators are free to drift from. A generated name carrying
// a plan id starts a new layer; anything else is a named piece of the layer above it.
public static class ArchBuiltPieces
{
	public static Dictionary<int, List<ArchBuiltPiece>> Index( Scene scene )
	{
		var index = new Dictionary<int, List<ArchBuiltPiece>>();

		foreach ( var root in ArchScene.FindRoots( scene ) )
		{
			foreach ( var child in root.Children )
			{
				Walk( index, child, 0, "" );
			}
		}

		return index;
	}

	static ArchBuiltPiece Walk( Dictionary<int, List<ArchBuiltPiece>> index, GameObject node, int owner, string under )
	{
		if ( ArchNames.TrySourceId( node.Name, out var id ) )
		{
			foreach ( var child in node.Children )
			{
				if ( Walk( index, child, id, "" ) is { } piece )
				{
					Filed( index, id ).Add( piece );
				}
			}

			return null;
		}

		var named = ArchParts.Joined( under, node.Name );

		var built = new ArchBuiltPiece
		{
			Name = node.Name,
			Piece = named,
			Owner = owner,
			Object = node,
			Renders = node.Components.Get<MeshComponent>( FindMode.EverythingInSelf ) is { Mesh: not null }
		};

		foreach ( var child in node.Children )
		{
			if ( Walk( index, child, owner, named ) is { } nested )
			{
				built.Children.Add( nested );
			}
		}

		// Nothing generated stands outside a building or a road, so a piece with no layer over it is a node
		// somebody added by hand and none of the stack's business.
		return owner == 0 ? null : built;
	}

	static List<ArchBuiltPiece> Filed( Dictionary<int, List<ArchBuiltPiece>> index, int owner )
	{
		if ( index.TryGetValue( owner, out var held ) )
		{
			return held;
		}

		return index[owner] = new List<ArchBuiltPiece>();
	}
}