editor/AdvancedMappingTools/Framework/ScaffoldingMounts.cs

Adds wall mount generation options to the scaffolding editor: plate size, range, bolts, connector, tag filters and material, plus the anchor sites along each bay and level and the mesh build call. Also exposes a debug draw and status readout.

Rough Code
namespace AdvancedMappingTools;
public sealed partial class ScaffoldingEditor : IAnchorAwareSettings
{
	[Property, Group( "Wall Mounts" ), WideMode, Description( "Generate mounts only where a real wall is found behind the platforms. Mount dimensions are world units." )]
	public bool MountsEnabled { get; set; }

	[Property, Group( "Wall Mounts" ), Range( 1, 4096, slider: false ), WideMode]
	public float MountRange { get; set; } = 256;

	[Property, Group( "Wall Mounts" ), Range( 0, 128, slider: false ), WideMode]
	public float MountMinimum { get; set; } = .01f;

	[Property, Group( "Wall Mounts" ), Range( 1, 512, slider: false ), WideMode]
	public float MountWidth { get; set; } = 12;

	[Property, Group( "Wall Mounts" ), Range( 1, 512, slider: false ), WideMode]
	public float MountHeight { get; set; } = 16;

	[Property, Group( "Wall Mounts" ), Range( .1f, 64, slider: false ), WideMode]
	public float MountThickness { get; set; } = 1;

	[Property, Group( "Wall Mounts" ), Range( 0, 128, slider: false ), WideMode]
	public float MountClearance { get; set; }

	[Property, Group( "Wall Mounts" ), Range( -180, 180, slider: false ), WideMode]
	public float MountRotation { get; set; }

	[Property, Group( "Wall Mounts" ), WideMode]
	public bool MountBolts { get; set; } = true;

	[Property, Group( "Wall Mounts" ), Range( .1f, 8, slider: false ), WideMode]
	public float MountBoltRadius { get; set; } = .65f;

	[Property, Group( "Wall Mounts" ), WideMode]
	public bool MountConnector { get; set; } = true;

	[Property, Group( "Wall Mounts" ), Range( .1f, 32, slider: false ), WideMode]
	public float MountConnectorWidth { get; set; } = 2;

	[Property, Group( "Wall Mounts" ), WideMode, Description( "Reject plates whose corners are not supported by a continuous surface." )]
	public bool MountCheckEdges { get; set; } = true;

	[Property, Group( "Wall Mounts" ), WideMode]
	public string MountIncludeTags { get; set; } = "";

	[Property, Group( "Wall Mounts" ), WideMode]
	public string MountExcludeTags { get; set; } = "";

	[Property, Group( "Wall Mounts" ), ResourceType( "vmat" ), WideMode]
	public string MountMaterial { get; set; }

	[Property, Group( "Wall Mounts" ), WideMode]
	public bool ShowMountDebug { get; set; }

	[System.Text.Json.Serialization.JsonIgnore, Property, ReadOnly, Group( "Wall Mounts" ), WideMode]
	public string MountStatus => MountsEnabled ? AnchorIntegration.Status( MountOwner ) : "Wall mounts off.";

	public AnchorOptions MountOptions() => new()
	{
		Range = MountRange,
		Minimum = MountMinimum,
		Width = MountWidth,
		Height = MountHeight,
		Thickness = MountThickness,
		Clearance = MountClearance,
		Rotation = MountRotation,
		Bolts = MountBolts,
		BoltRadius = MountBoltRadius,
		Connector = MountConnector,
		ConnectorWidth = MountConnectorWidth,
		CheckEdges = MountCheckEdges,
		IncludeTags = MountIncludeTags,
		ExcludeTags = MountExcludeTags,
		Material = MountMaterial
	};
	[Property, Group( "Wall Mounts" ), Range( 1, 16, slider: false ), WideMode]
	public int MountEveryBays { get; set; } = 2;

	[System.Text.Json.Serialization.JsonIgnore]
	public string MountOwner => "Scaffolding Mounts";

	public IEnumerable<AnchorSite> MountSites()
	{
		var length = MathF.Max( Length, 32 );
		var depth = MathF.Max( Depth, 8 );
		var height = MathF.Max( Height, 16 );
		var levels = Math.Clamp( Levels, 1, 16 );
		var bays = (int)MathF.Max( MathF.Round( length / Math.Clamp( BayWidth, 16, 256 ) ), 1 );
		var every = Math.Clamp( MountEveryBays, 1, 16 );
		for ( var level = 1; level <= levels; level++ )
			for ( var bay = 0; bay <= bays; bay++ )
				if ( bay % every == 0 || bay == bays )
					yield return new( $"level{level}_bay{bay}", new Vector3( -length * .5f + length * bay / bays, -depth * .5f, height * level / levels ), new Vector3( 0, -1, 0 ) );
	}

	void BuildSurfaceMounts( MeshKit kit, Transform frame )
	{
		if ( !MountsEnabled )
			return;
		var context = new GenContext( new() )
		{
			Surface = new( SurfaceSolver.Begin( SceneEditorSession.Active?.Scene ?? Game.ActiveScene ), frame )
		};
		var root = context.Group( "root", "Scaffolding Mounts", "SurfaceMount", "mount" );
		AnchorIntegration.Append( root, (IAnchorAwareSettings)this, context );
		kit.ImportMesh( root.Flatten( frame ), Transform.Zero );
	}

	public override IReadOnlySet<string> UnusedOptions
	{
		get
		{
			var h = new HashSet<string>( base.UnusedOptions );
			h.UnionWith( AnchorIntegration.Hidden( this ) );
			if ( !MountsEnabled )
				h.Add( nameof( MountEveryBays ) );
			return h;
		}
	}

	public override bool ChangesOtherSettings( string property ) => base.ChangesOtherSettings( property ) || property is nameof( MountsEnabled ) or nameof( MountBolts ) or nameof( MountConnector );
	public override void OnUpdate( SceneTrace trace )
	{
		base.OnUpdate( trace );
		if ( MountsEnabled && ShowMountDebug )
			SurfaceDebug.Draw( MountOwner );
	}
}