Editor/Layers/ArchLayerTree.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sunless.Architecture;
public sealed class ArchLayerNode {
public ArchLayerRef? Ref { get; init; }
public ArchKind Kind { get; init; }
public ArchLayerStage Stage { get; init; }
public ArchLayerDomain Domain { get; init; }
public object Payload { get; init; }
public string Name { get; init; } = "";
public bool Enabled { get; init; } = true;
public bool Locked { get; init; }
public int Order { get; init; }
public int Floor { get; init; } = int.MinValue;
public ArchLayerNode Parent { get; internal set; }
public List<ArchLayerNode> Children { get; } = new();
public ArchBuilding Building { get; init; }
public ArchRoom Room { get; init; }
public bool Virtual => Payload is null;
public string DisplayName => ArchLayerNames.DisplayName( this );
}
public sealed class ArchLayerDomainGroup {
public ArchLayerDomain Domain { get; init; }
public string Name { get; init; } = "";
public List<ArchLayerNode> Children { get; } = new();
}
public sealed class ArchLayerTree {
public List<ArchLayerDomainGroup> Domains { get; } = new();
readonly Dictionary<int, ArchLayerNode> byId = new();
readonly Dictionary<object, ArchLayerNode> byPayload = new();
readonly Dictionary<(int Building, int Floor), ArchLayerNode> stories = new();
public IReadOnlyList<ArchLayerLink> Links { get; private set; } = Array.Empty<ArchLayerLink>();
public ArchLayerNode Find( int id ) => byId.TryGetValue( id, out var node ) ? node : null;
public ArchLayerNode Find( object payload ) {
return payload is null ? null : byPayload.TryGetValue( payload, out var node ) ? node : null;
}
public object Resolve( ArchLayerRef layer ) => byId.TryGetValue( layer.ItemId, out var node ) ? node.Payload : null;
public string Breadcrumb( ArchLayerNode node ) {
if ( node is null ) {
return "";
}
var parts = new List<string>();
for ( var current = node; current is not null; current = current.Parent ) {
parts.Add( current.DisplayName );
}
parts.Reverse();
return string.Join( " / ", parts );
}
public object StableKey( ArchLayerNode node ) {
return node.Payload ?? $"story:{node.Building?.Id}:{node.Floor}";
}
public ArchLayerRecord Record( ArchPlan plan, ArchLayerNode node ) {
if ( node?.Ref is not { } layer ) {
return null;
}
var record = plan.Layers.FirstOrDefault( entry => entry.ItemId == layer.ItemId );
if ( record is null ) {
record = new ArchLayerRecord {
ItemId = layer.ItemId,
ParentId = layer.ParentId,
Kind = layer.Kind,
Stage = node.Stage,
Order = node.Order,
Enabled = node.Enabled,
Locked = node.Locked,
};
plan.Layers.Add( record );
}
return record;
}
public bool Reorder( ArchPlan plan, ArchLayerRef moving, int anchorId, bool below ) {
if ( !byId.TryGetValue( moving.ItemId, out var node ) || !byId.TryGetValue( anchorId, out var anchor ) ) {
return false;
}
if ( ReferenceEquals( node, anchor ) || !ReferenceEquals( node.Parent, anchor.Parent ) ) {
return false;
}
var siblings = (node.Parent?.Children ?? Domains.FirstOrDefault( domain => domain.Domain == node.Domain )?.Children)
?.Where( child => child.Ref is not null )
.ToList();
if ( siblings is null ) {
return false;
}
siblings.Remove( node );
var at = siblings.IndexOf( anchor );
if ( at < 0 ) {
return false;
}
siblings.Insert( below ? at + 1 : at, node );
for ( var index = 0; index < siblings.Count; index++ ) {
Record( plan, siblings[index] ).Order = index;
}
return true;
}
public bool Reparent( ArchPlan plan, ArchLayerRef layer, int newParentId ) {
if ( !byId.TryGetValue( layer.ItemId, out var node ) || node.Payload is null ) {
return false;
}
if ( newParentId == 0 ) {
ArchLayerGroups.Leave( plan, layer.ItemId );
return true;
}
if ( layer.ItemId == newParentId || !byId.TryGetValue( newParentId, out var parentNode ) || parentNode.Payload is null ) {
return false;
}
if ( parentNode.Payload is ArchSiteAssembly group ) {
ArchLayerGroups.Join( plan, group, layer.ItemId );
return true;
}
if ( !ArchLayerRules.CanParent( parentNode.Kind, layer.Kind ).Allowed ) {
return false;
}
ArchLayerGroups.Leave( plan, layer.ItemId );
if ( !MovePayload( plan, node.Payload, parentNode.Payload ) ) {
return false;
}
var record = plan.Layers.FirstOrDefault( entry => entry.ItemId == layer.ItemId );
if ( record is null ) {
record = new ArchLayerRecord { ItemId = layer.ItemId, ParentId = newParentId, Kind = layer.Kind, Stage = node.Stage };
plan.Layers.Add( record );
}
record.ParentId = newParentId;
return true;
}
static bool Unfile( ArchPlan plan, ArchWall wall ) {
foreach ( var room in plan.AllRooms() ) {
if ( room.Walls.Remove( wall ) ) {
return true;
}
}
foreach ( var roof in plan.AllRoofs() ) {
if ( roof.Walls.Remove( wall ) ) {
return true;
}
}
return false;
}
static bool Unfile( ArchPlan plan, ArchStairPart stair ) {
foreach ( var room in plan.AllRooms() ) {
if ( room.Stairs.Remove( stair ) || room.Porches.Any( porch => porch.Stairs.Remove( stair ) ) ) {
return true;
}
}
return plan.Buildings.SelectMany( building => building.Platforms ).Any( platform => platform.Stairs.Remove( stair ) );
}
static bool Unfile( ArchPlan plan, ArchPillarPart pillar ) {
return plan.AllRooms().Any( room => room.Pillars.Remove( pillar ) || room.Porches.Any( porch => porch.Pillars.Remove( pillar ) ) );
}
static bool Unfile( ArchPlan plan, ArchTrimPart trim ) {
return plan.AllRooms().Any( room => room.Trims.Remove( trim ) || room.Porches.Any( porch => porch.Trims.Remove( trim ) ) );
}
static bool MovePayload( ArchPlan plan, object payload, object newParent ) {
switch (payload, newParent) {
case (ArchRoom room, ArchBuilding building ):
if ( plan.OwnerOf( room ) is not { } fromRoom ) return false;
fromRoom.Rooms.Remove( room );
building.Rooms.Add( room );
return true;
case (ArchWall wall, ArchRoom room ):
if ( !Unfile( plan, wall ) ) return false;
room.Walls.Add( wall );
return true;
case (ArchWall wall, ArchRoofPart roof ):
if ( !Unfile( plan, wall ) ) return false;
roof.Walls.Add( wall );
return true;
case (ArchOpening opening, ArchWall wall ):
if ( plan.AllWalls().FirstOrDefault( candidate => candidate.Openings.Contains( opening ) ) is not { } fromOpening ) return false;
fromOpening.Openings.Remove( opening );
wall.Openings.Add( opening );
return true;
case (ArchWallModPart modifier, ArchWall host ):
if ( plan.AllWalls().FirstOrDefault( candidate => candidate.Modifiers.Contains( modifier ) ) is not { } fromModifier ) return false;
fromModifier.Modifiers.Remove( modifier );
host.Modifiers.Add( modifier );
return true;
case (ArchStairPart stair, ArchRoom room ):
if ( !Unfile( plan, stair ) ) return false;
room.Stairs.Add( stair );
return true;
case (ArchTrimPart trim, ArchRoom room ):
if ( !Unfile( plan, trim ) ) return false;
room.Trims.Add( trim );
return true;
case (ArchPillarPart pillar, ArchRoom room ):
if ( !Unfile( plan, pillar ) ) return false;
room.Pillars.Add( pillar );
return true;
case (ArchStairPart stair, ArchPorchPart porch ):
if ( !Unfile( plan, stair ) ) return false;
porch.Stairs.Add( stair );
return true;
case (ArchPillarPart pillar, ArchPorchPart porch ):
if ( !Unfile( plan, pillar ) ) return false;
porch.Pillars.Add( pillar );
return true;
case (ArchTrimPart trim, ArchPorchPart porch ):
if ( !Unfile( plan, trim ) ) return false;
porch.Trims.Add( trim );
return true;
case (ArchCutPart, ArchPorchPart ):
return true;
case (ArchBeamPart beam, ArchRoom room ):
if ( plan.AllRooms().FirstOrDefault( candidate => candidate.Beams.Contains( beam ) ) is not { } fromBeam ) return false;
fromBeam.Beams.Remove( beam );
room.Beams.Add( beam );
return true;
case (ArchPorchPart porch, ArchRoom room ):
if ( plan.AllRooms().FirstOrDefault( candidate => candidate.Porches.Contains( porch ) ) is not { } fromPorch ) return false;
fromPorch.Porches.Remove( porch );
room.Porches.Add( porch );
return true;
case (ArchApproachPart approach, ArchRoom room ):
if ( plan.AllRooms().FirstOrDefault( candidate => candidate.Approaches.Contains( approach ) ) is not { } fromApproach ) return false;
fromApproach.Approaches.Remove( approach );
room.Approaches.Add( approach );
return true;
case (ArchRoofPart roof, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.Roofs.Contains( roof ) ) is not { } fromRoof ) return false;
fromRoof.Roofs.Remove( roof );
building.Roofs.Add( roof );
return true;
case (ArchRoofLightPart light, ArchRoofPart roof ):
if ( plan.Buildings.SelectMany( candidate => candidate.Roofs ).FirstOrDefault( candidate => candidate.Lights.Contains( light ) ) is not { } fromLight ) return false;
fromLight.Lights.Remove( light );
roof.Lights.Add( light );
return true;
case (ArchPlatformPart platform, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.Platforms.Contains( platform ) ) is not { } fromPlatform ) return false;
fromPlatform.Platforms.Remove( platform );
building.Platforms.Add( platform );
return true;
case (ArchStairPart stair, ArchPlatformPart platform ):
if ( !Unfile( plan, stair ) ) return false;
platform.Stairs.Add( stair );
return true;
case (ArchDownpipePart pipe, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.Downpipes.Contains( pipe ) ) is not { } fromPipe ) return false;
fromPipe.Downpipes.Remove( pipe );
building.Downpipes.Add( pipe );
return true;
case (ArchPipePart run, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.Pipes.Contains( run ) ) is not { } fromRun ) return false;
fromRun.Pipes.Remove( run );
building.Pipes.Add( run );
return true;
case (ArchPipeBracketPart bracket, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.Brackets.Contains( bracket ) ) is not { } fromBracket ) return false;
fromBracket.Brackets.Remove( bracket );
building.Brackets.Add( bracket );
return true;
case (ArchFencePart fence, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.Fences.Contains( fence ) ) is not { } fromFence ) return false;
fromFence.Fences.Remove( fence );
building.Fences.Add( fence );
return true;
case (ArchLadderPart ladder, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.Ladders.Contains( ladder ) ) is not { } fromLadder ) return false;
fromLadder.Ladders.Remove( ladder );
building.Ladders.Add( ladder );
return true;
case (ArchBalconyPart balcony, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.Balconies.Contains( balcony ) ) is not { } fromBalcony ) return false;
fromBalcony.Balconies.Remove( balcony );
building.Balconies.Add( balcony );
return true;
case (ArchExteriorStairPart flight, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.ExteriorStairs.Contains( flight ) ) is not { } fromFlight ) return false;
fromFlight.ExteriorStairs.Remove( flight );
building.ExteriorStairs.Add( flight );
return true;
case (ArchCutPart cut, ArchBuilding building ):
if ( plan.Buildings.FirstOrDefault( candidate => candidate.Cuts.Contains( cut ) ) is not { } fromCut ) return false;
fromCut.Cuts.Remove( cut );
building.Cuts.Add( cut );
return true;
case (ArchBridgePart bridge, ArchRoadPart road ):
if ( plan.Roads().FirstOrDefault( candidate => candidate.Bridges.Contains( bridge ) ) is not { } fromBridge ) return false;
fromBridge.Bridges.Remove( bridge );
road.Bridges.Add( bridge );
return true;
case (ArchTunnelPart tunnel, ArchRoadPart road ):
if ( plan.Roads().FirstOrDefault( candidate => candidate.Tunnels.Contains( tunnel ) ) is not { } fromTunnel ) return false;
fromTunnel.Tunnels.Remove( tunnel );
road.Tunnels.Add( tunnel );
return true;
default:
return false;
}
}
public bool IsEnabled( object payload ) {
return payload is null || byPayload.TryGetValue( payload, out var node ) && node.Enabled;
}
public bool IsEnabled( int id ) {
return byId.TryGetValue( id, out var node ) && node.Enabled;
}
public bool IsLocked( int id ) {
return byId.TryGetValue( id, out var node ) && node.Locked;
}
public bool IsLocked( object payload ) {
return payload is not null && byPayload.TryGetValue( payload, out var node ) && node.Locked;
}
// Walks up — locking a parent freezes all children
public bool Frozen( int itemId ) {
if ( itemId == 0 || !byId.TryGetValue( itemId, out var found ) ) {
return false;
}
for ( var node = found; node is not null; node = node.Parent ) {
if ( node.Locked ) {
return true;
}
}
return false;
}
public bool AnyFrozen() => byId.Values.Any( node => node.Locked );
public IReadOnlyList<string> Problems( ArchLayerNode node ) {
var problems = new List<string>();
if ( node?.Ref is not { } nodeRef ) {
return problems;
}
foreach ( var link in Links.Where( link => link.Required && link.SourceId == nodeRef.ItemId ) ) {
if ( !byId.ContainsKey( link.TargetId ) ) {
problems.Add( $"Required link {link.SourcePort} → {link.TargetPort} (id {link.TargetId}) does not resolve." );
}
}
return problems;
}
public IReadOnlySet<int> DirtyClosure( int itemId ) {
var dirty = new HashSet<int> { itemId };
if ( byId.TryGetValue( itemId, out var node ) ) {
CollectDescendants( node, dirty );
}
foreach ( var link in Links.Where( link => link.Required && link.TargetId == itemId ) ) {
dirty.Add( link.SourceId );
}
foreach ( var entry in byId.Values ) {
if ( OwnsEffectsOn( entry.Payload, itemId ) && entry.Ref is { } entryRef ) {
dirty.Add( entryRef.ItemId );
}
}
return dirty;
}
static void CollectDescendants( ArchLayerNode node, HashSet<int> into ) {
foreach ( var child in node.Children ) {
if ( child.Ref is { } childRef ) {
into.Add( childRef.ItemId );
}
CollectDescendants( child, into );
}
}
static bool OwnsEffectsOn( object payload, int ownerId ) {
return payload switch {
ArchBuilding building => building.Cutouts.Any( cutout => cutout.OwnerId == ownerId ),
ArchRoom room => room.Walls.SelectMany( wall => wall.Openings ).Any( opening => opening.OwnerId == ownerId ),
_ => false
};
}
public static ArchLayerTree Project( ArchPlan plan ) {
var tree = new ArchLayerTree();
if ( plan is null ) {
return tree;
}
var kinds = ArchKinds.Load();
var entries = new List<Entry>();
var byId = new Dictionary<int, Entry>();
void Register( object payload, int id, int parent, ArchKind kind, ArchBuilding building, ArchRoom room,
int floor = int.MinValue, ArchLayerStage? stage = null ) {
var entry = new Entry {
Payload = payload,
Id = id,
ParentId = parent,
Kind = kind,
Stage = stage,
Name = kinds.NameOf( kind, payload, id ),
Building = building,
Room = room,
Floor = floor,
};
entries.Add( entry );
byId[id] = entry;
}
foreach ( var building in plan.Buildings ) {
Register( building, building.Id, 0, ArchKind.Building, building, null );
foreach ( var room in building.Rooms ) {
var kind = room.Spans ? ArchKind.Walkway : ArchKind.Room;
Register( room, room.Id, building.Id, kind, building, room, room.Floor );
foreach ( var wall in room.Walls ) {
Register( wall, wall.Id, room.Id, ArchKind.Wall, building, room );
foreach ( var opening in wall.Openings ) {
Register( opening, opening.Id, wall.Id, ArchKind.Opening, building, room );
}
foreach ( var modifier in wall.Modifiers ) {
Register( modifier, modifier.Id, wall.Id, ArchKind.WallMod, building, room,
stage: modifier.Carves ? ArchLayerStage.Void : ArchLayerStage.Structure );
}
}
foreach ( var stair in room.Stairs ) {
Register( stair, stair.Id, room.Id, ArchKind.Stair, building, room );
}
foreach ( var trim in room.Trims ) {
Register( trim, trim.Id, room.Id, ArchKind.Trim, building, room );
}
foreach ( var pillar in room.Pillars ) {
Register( pillar, pillar.Id, room.Id, ArchKind.Pillar, building, room );
}
foreach ( var span in room.PierSpans ) {
Register( span, span.Id, room.Id, ArchKind.Span, building, room );
}
foreach ( var beam in room.Beams ) {
Register( beam, beam.Id, room.Id, ArchKind.Beam, building, room );
}
foreach ( var cabinets in room.Cabinets ) {
Register( cabinets, cabinets.Id, room.Id, ArchKind.Cabinet, building, room );
}
foreach ( var porch in room.Porches ) {
Register( porch, porch.Id, room.Id, ArchKind.Porch, building, room );
foreach ( var stair in porch.Stairs ) {
Register( stair, stair.Id, porch.Id, ArchKind.Stair, building, room );
}
foreach ( var pillar in porch.Pillars ) {
Register( pillar, pillar.Id, porch.Id, ArchKind.Pillar, building, room );
}
foreach ( var trim in porch.Trims ) {
Register( trim, trim.Id, porch.Id, ArchKind.Trim, building, room );
}
}
foreach ( var approach in room.Approaches ) {
Register( approach, approach.Id, room.Id, ArchKind.Approach, building, room );
}
}
foreach ( var roof in building.Roofs ) {
Register( roof, roof.Id, building.Id, ArchKind.Roof, building, null );
foreach ( var light in roof.Lights ) {
Register( light, light.Id, roof.Id, ArchKind.RoofLight, building, null );
}
foreach ( var wall in roof.Walls ) {
Register( wall, wall.Id, roof.Id, ArchKind.Wall, building, null );
foreach ( var opening in wall.Openings ) {
Register( opening, opening.Id, wall.Id, ArchKind.Opening, building, null );
}
foreach ( var modifier in wall.Modifiers ) {
Register( modifier, modifier.Id, wall.Id, ArchKind.WallMod, building, null,
stage: modifier.Carves ? ArchLayerStage.Void : ArchLayerStage.Structure );
}
}
}
foreach ( var pipe in building.Downpipes ) {
Register( pipe, pipe.Id, building.Id, ArchKind.Downpipe, building, null );
}
foreach ( var run in building.Pipes ) {
Register( run, run.Id, building.Id, ArchKind.Pipe, building, null );
}
foreach ( var bracket in building.Brackets ) {
Register( bracket, bracket.Id, building.Id, ArchKind.Bracket, building, null );
}
foreach ( var fence in building.Fences ) {
Register( fence, fence.Id, building.Id, ArchKind.Fence, building, null );
}
foreach ( var platform in building.Platforms ) {
Register( platform, platform.Id, building.Id, ArchKind.Platform, building, null );
foreach ( var stair in platform.Stairs ) {
Register( stair, stair.Id, platform.Id, ArchKind.CarvedStair, building, null );
}
}
foreach ( var ladder in building.Ladders ) {
Register( ladder, ladder.Id, building.Id, ArchKind.Ladder, building, null );
}
foreach ( var balcony in building.Balconies ) {
Register( balcony, balcony.Id, building.Id, ArchKind.Balcony, building, null );
}
foreach ( var flight in building.ExteriorStairs ) {
Register( flight, flight.Id, building.Id, ArchKind.ExteriorStair, building, null );
}
foreach ( var cut in building.Cuts ) {
Register( cut, cut.Id, building.Id, ArchKind.Cut, building, null,
stage: cut.Mode != ArchZoneMode.Carve ? ArchLayerStage.Finish : ArchLayerStage.Void );
}
}
foreach ( var road in plan.Roads() ) {
Register( road, road.Id, 0, ArchKind.Road, null, null );
foreach ( var crossing in road.Crossings ) {
Register( crossing, crossing.Id, road.Id, ArchKind.Crossing, null, null );
}
foreach ( var bridge in road.Bridges ) {
Register( bridge, bridge.Id, road.Id, ArchKind.Bridge, null, null );
}
foreach ( var tunnel in road.Tunnels ) {
Register( tunnel, tunnel.Id, road.Id, ArchKind.Tunnel, null, null );
}
foreach ( var cut in road.Cuts ) {
Register( cut, cut.Id, road.Id, ArchKind.Cut, null, null,
stage: cut.Mode != ArchZoneMode.Carve ? ArchLayerStage.Finish : ArchLayerStage.Void );
}
}
foreach ( var unit in plan.Units ) {
if ( unit is ArchBuilding or ArchRoadPart ) {
continue;
}
var owner = kinds.Parent( unit.Kind, unit );
Register( unit, unit.Id, owner, unit.Kind, plan.FindBuilding( owner ), null,
stage: kinds.Stage( unit.Kind, unit ) );
foreach ( var cut in unit.Cuts ) {
Register( cut, cut.Id, unit.Id, ArchKind.Cut, null, null,
stage: cut.Mode != ArchZoneMode.Carve ? ArchLayerStage.Finish : ArchLayerStage.Void );
}
}
foreach ( var record in plan.Layers ) {
if ( byId.TryGetValue( record.ItemId, out var entry ) && record.ParentId != record.ItemId ) {
entry.ParentId = record.ParentId;
entry.Kind = record.Kind;
entry.Stage = record.Stage;
entry.Enabled = record.Enabled;
entry.Locked = record.Locked;
entry.Order = record.Order;
}
}
var nodes = new Dictionary<int, ArchLayerNode>();
foreach ( var entry in entries ) {
var node = new ArchLayerNode {
Ref = new ArchLayerRef { ItemId = entry.Id, Kind = entry.Kind, ParentId = entry.ParentId },
Kind = entry.Kind,
Stage = entry.Stage ?? kinds.Stage( entry.Kind ),
Domain = kinds.Domain( entry.Kind ),
Payload = entry.Payload,
Name = entry.Name,
Enabled = entry.Enabled,
Locked = entry.Locked,
Order = entry.Order,
Floor = entry.Floor,
Building = entry.Building,
Room = entry.Room,
};
nodes[entry.Id] = node;
tree.byId[entry.Id] = node;
tree.byPayload[entry.Payload] = node;
}
var memberOf = new Dictionary<int, ArchLayerNode>();
foreach ( var assembly in plan.Assemblies ) {
var node = new ArchLayerNode {
Ref = new ArchLayerRef { ItemId = assembly.Id, Kind = ArchKind.Assembly, ParentId = 0 },
Kind = ArchKind.Assembly,
Stage = kinds.Stage( ArchKind.Assembly ),
Domain = DomainOf( assembly, byId, kinds ),
Payload = assembly,
Name = assembly.Name,
};
nodes[assembly.Id] = node;
tree.byId[assembly.Id] = node;
tree.byPayload[assembly] = node;
foreach ( var childId in assembly.Children.Where( childId => childId != assembly.Id ) ) {
memberOf[childId] = node;
}
}
foreach ( var instance in plan.Instances ) {
var node = new ArchLayerNode {
Ref = new ArchLayerRef { ItemId = instance.Id, Kind = ArchKind.AssetInstance, ParentId = 0 },
Kind = ArchKind.AssetInstance,
Stage = kinds.Stage( ArchKind.AssetInstance ),
Domain = ArchLayerDomain.Connections,
Payload = instance,
Name = instance.Name,
};
nodes[instance.Id] = node;
tree.byId[instance.Id] = node;
tree.byPayload[instance] = node;
tree.AddRoot( node );
}
foreach ( var building in plan.Buildings ) {
if ( !nodes.TryGetValue( building.Id, out var buildingNode ) ) {
continue;
}
foreach ( var floor in entries
.Where( entry => entry.ParentId == building.Id && (entry.Kind == ArchKind.Room || entry.Kind == ArchKind.Walkway) )
.Select( entry => entry.Floor )
.Distinct()
.OrderBy( floor => floor ) ) {
var story = new ArchLayerNode {
Kind = ArchKind.Story,
Stage = ArchLayerStage.Shape,
Domain = ArchLayerDomain.Buildings,
Name = $"Level {floor}",
Floor = floor,
Building = building,
};
story.Parent = buildingNode;
buildingNode.Children.Add( story );
tree.stories[(building.Id, floor)] = story;
}
}
var typedHost = new Dictionary<int, ArchLayerNode>();
foreach ( var entry in entries ) {
var node = nodes[entry.Id];
var host = entry.ParentId != 0 && nodes.TryGetValue( entry.ParentId, out var parent ) ? parent : null;
if ( host is not null
&& host.Kind == ArchKind.Building
&& (node.Kind == ArchKind.Room || node.Kind == ArchKind.Walkway)
&& tree.stories.TryGetValue( (entry.ParentId, entry.Floor), out var story ) ) {
host = story;
}
if ( host is not null ) {
typedHost[entry.Id] = host;
}
if ( memberOf.TryGetValue( entry.Id, out var group ) ) {
group.Children.Add( node );
node.Parent = group;
continue;
}
if ( host is null ) {
tree.AddRoot( node );
continue;
}
host.Children.Add( node );
node.Parent = host;
}
foreach ( var assembly in plan.Assemblies ) {
foreach ( var nested in plan.Assemblies.Where( inner => inner.Id != assembly.Id && assembly.Children.Contains( inner.Id ) ) ) {
var child = nodes[nested.Id];
nodes[assembly.Id].Children.Add( child );
child.Parent = nodes[assembly.Id];
}
}
foreach ( var assembly in plan.Assemblies ) {
var node = nodes[assembly.Id];
var member = node.Children.OrderBy( child => Membership( assembly, child ) ).ToList();
node.Children.Clear();
node.Children.AddRange( member );
foreach ( var link in plan.Links.Where( link => link.SourceId == assembly.Id ) ) {
tree.byId.TryGetValue( link.TargetId, out var targetNode );
if ( targetNode is null || Within( targetNode, node ) ) {
continue;
}
node.Children.Add( new ArchLayerNode {
Kind = ArchKind.Assembly,
Stage = ArchLayerStage.Reference,
Domain = node.Domain,
Payload = new ArchLayerReference { SourcePort = link.SourcePort, TargetId = link.TargetId, TargetPort = link.TargetPort },
Name = $"{link.SourcePort} → {targetNode.Name}",
Parent = node,
} );
}
if ( node.Parent is null && Homed( assembly, typedHost ) is { } home ) {
home.Children.Add( node );
node.Parent = home;
}
if ( node.Parent is null ) {
tree.AddRoot( node );
}
}
foreach ( var group in tree.Domains ) {
Sort( group.Children );
}
tree.Links = plan.Links;
return tree;
}
static void Sort( List<ArchLayerNode> children ) {
if ( children.Count > 1 ) {
var ordered = children.OrderBy( child => child.Order ).ToList();
children.Clear();
children.AddRange( ordered );
}
foreach ( var child in children ) {
Sort( child.Children );
}
}
static bool Within( ArchLayerNode node, ArchLayerNode ancestor ) {
for ( var current = node; current is not null; current = current.Parent ) {
if ( ReferenceEquals( current, ancestor ) ) {
return true;
}
}
return false;
}
// Group hangs where its members came from; mixed hosts or self-containing groups root at domain
static ArchLayerNode Homed( ArchSiteAssembly assembly, Dictionary<int, ArchLayerNode> typedHost ) {
ArchLayerNode home = null;
foreach ( var childId in assembly.Children ) {
if ( !typedHost.TryGetValue( childId, out var host ) ) {
return null;
}
home ??= host;
if ( !ReferenceEquals( home, host ) ) {
return null;
}
}
for ( var walk = home; walk is not null; walk = walk.Parent ) {
if ( walk.Ref is { } layer && assembly.Children.Contains( layer.ItemId ) ) {
return null;
}
}
return home;
}
static int Membership( ArchSiteAssembly assembly, ArchLayerNode child ) {
var at = child.Ref is { } layer ? assembly.Children.IndexOf( layer.ItemId ) : -1;
return at < 0 ? int.MaxValue : at;
}
static ArchLayerDomain DomainOf( ArchSiteAssembly assembly, Dictionary<int, Entry> byId, ArchKinds kinds ) {
foreach ( var childId in assembly.Children ) {
if ( byId.TryGetValue( childId, out var entry ) ) {
return kinds.Domain( entry.Kind );
}
}
return ArchLayerDomain.Connections;
}
void AddRoot( ArchLayerNode node ) {
var group = Domains.FirstOrDefault( domain => domain.Domain == node.Domain );
if ( group is null ) {
group = new ArchLayerDomainGroup {
Domain = node.Domain,
Name = node.Domain switch {
ArchLayerDomain.Buildings => "Buildings",
ArchLayerDomain.Connections => "Connections",
_ => "Infrastructure"
}
};
Domains.Add( group );
}
group.Children.Add( node );
}
sealed class Entry {
public object Payload;
public int Id;
public int ParentId;
public ArchKind Kind;
public ArchLayerStage? Stage;
public bool Enabled = true;
public bool Locked;
public int Order;
public string Name;
public int Floor = int.MinValue;
public ArchBuilding Building;
public ArchRoom Room;
}
}