Editor utility that detects pipe fittings in an architectural pipe plan. It finds fittings at node IDs by collecting adjacent run legs, determines fitting kind (Flange, Coupling, Elbow, Tee, Reducer) based on leg count, run diameters and directions, and returns fitting metadata including position and associated runs.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public enum ArchPipeFittingKind
{
Flange,
Coupling,
Elbow,
Tee,
Reducer
}
public sealed class ArchPipeFitting
{
public int NodeId { get; init; }
public Vector3 Position { get; init; }
public ArchPipeFittingKind Kind { get; init; }
public List<int> Runs { get; init; } = new();
}
public static class ArchPipeFittings
{
readonly record struct Leg( ArchPipePart Run, Vector3 Direction );
public static List<ArchPipeFitting> Resolve( ArchPlan plan, ArchPipePart part )
{
return part.Nodes
.Where( node => node.Id != 0 )
.Select( node => At( plan, node.Id ) )
.Where( fitting => fitting is not null )
.DistinctBy( fitting => fitting.NodeId )
.ToList();
}
public static ArchPipeFitting At( ArchPlan plan, int nodeId )
{
var legs = new List<Leg>();
var position = Vector3.Zero;
var found = false;
foreach ( var run in ArchPipe.All( plan ) )
{
for ( var index = 0; index < run.Nodes.Count; index++ )
{
if ( run.Nodes[index].Id != nodeId )
{
continue;
}
position = run.Nodes[index].Position;
found = true;
if ( index > 0 )
{
legs.Add( new Leg( run, (run.Nodes[index - 1].Position - position).Normal ) );
}
if ( index < run.Nodes.Count - 1 )
{
legs.Add( new Leg( run, (run.Nodes[index + 1].Position - position).Normal ) );
}
}
}
if ( !found || legs.Count == 0 )
{
return null;
}
var runs = legs.Select( leg => leg.Run ).Distinct().ToList();
var diameters = runs.Select( run => run.Diameter ).Distinct().Count();
var kind = legs.Count switch
{
1 => ArchPipeFittingKind.Flange,
>= 3 => ArchPipeFittingKind.Tee,
_ when diameters > 1 => ArchPipeFittingKind.Reducer,
_ when Vector3.Dot( legs[0].Direction, legs[1].Direction ) > -0.999f => ArchPipeFittingKind.Elbow,
_ => ArchPipeFittingKind.Coupling
};
return new ArchPipeFitting
{
NodeId = nodeId,
Position = position,
Kind = kind,
Runs = runs.Select( run => run.Id ).ToList()
};
}
}