Editor/placeobj.cs
using Editor;
using Editor.Assets;
using Editor.Audio;
using Editor.Inspectors;
using Editor.MapEditor;
using Editor.MeshEditor;
using Editor.ShaderGraph.Nodes;
using Sandbox;
using Sandbox.Audio;
using Sandbox.UI;
using Sandbox.Utility;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Editor.Inspectors.AssetInspector;
using static Editor.TreeNode;

//TODO: Ignore 1 grid axis when normal surface is offgrid;
//Prefab selection

namespace Editor.PrefabPlacer;

[CanEdit(typeof(PlaceObj))] // Asset file extension, can do typeof(Class) for non-assets
public class PlacerInspector : InspectorWidget
{
	PlaceObj placer;
	public static Label header;

    // If this isn't an Asset Inspector, use CharacterInspector(SerializedObject so) : base(so)
    public PlacerInspector( SerializedObject so ) : base( so )
    {
		if ( so.Targets.FirstOrDefault() is not PlaceObj tool )
			return;
		placer = tool;
        Layout = Layout.Column();
        Layout.Margin = 4;
        Layout.Spacing = 4;

        Rebuild();
    }

    // Rebuild the UI every hotload, so we catch changes to the Asset
    [EditorEvent.Hotload]
    void Rebuild()
    {

        Layout?.Clear( true );

        // Create a header
        header = Layout.Add( new Label( "Select an object", this ) );
        header.SetStyles( "font-size: 38px; font-weight: 500; font-family: Poppins" );



		var button = Layout.Add( new Button.Primary( "Open all", "list_alt", this )
		{
			Clicked = () => { placer.OpenAllFunctions(); }
		} );


		//button.Pressed = () => PlaceObj.OpenAllFunctions(button.ScreenPosition);
		//currentPrefabText = new Label() { Text = "none" };
		//window.Layout.Add( currentPrefabText );
		Layout.AddSpacingCell( 8 );

		var surfaceCheckbox = Layout.Add( new Checkbox( "Align to surface", this ) );
		surfaceCheckbox.StateChanged += ( CheckState state ) =>
		{
			PlaceObj.useSurfaceNormal = state == CheckState.On;
		};

		var selectCheckbox = Layout.Add( new Checkbox( "Select when placed", this ) );
		selectCheckbox.StateChanged += ( CheckState state ) =>
		{
			PlaceObj.selectOnPlace = state == CheckState.On;
		};

		var customSwitch = Layout.Add( new Checkbox( "Show only custom prefabs (BROKEN)", this ));
		Layout.AddSpacingCell( 8 );

		var deltaFloat = Layout.Add( new IntProperty( this ) { HighlightColor = Theme.Yellow, Icon = "height", ToolTip = "Distance from surface", Value = 8 } );
		deltaFloat.OnChildValuesChanged += ( Widget w ) => PlaceObj.placeDeltaDistance = deltaFloat.Value;
		 Layout.AddStretchCell();  
    }


}





[EditorTool("tools.ketal.prefab-placer")] // this class is an editor tool
[Title( "Object placement tool" )] // title of your tool
[Icon( "dashboard_customize" )]
[Group( "-1" )]
public partial class PlaceObj : EditorTool
{

	Vector3 placeOffsetPos;


	Rotation normalRot;

	public GameObject prefabTemplate;

	public static float placeDeltaDistance;
	public static bool useSurfaceNormal = false;
	public static bool selectOnPlace = false;
	public bool showOnlyCustom;


	PrefabFile prefabItem;

	Widget window;


	private Layout ControlLayout { get; set; }

	[Shortcut( "tools.ketal.prefab-placer", "Shift+E", typeof( SceneViewportWidget ) )]
	public static void ActivateTool()
	{
		EditorToolManager.SetTool( nameof( PlaceObj ) );
	}

	public override void OnDisabled()
	{
		Selection.Clear();
	}

	public override void OnEnabled()
	{
		useSurfaceNormal = false;
		placeDeltaDistance = 8f;
		selectOnPlace = false;

		AllowGameObjectSelection = false;
		Selection.Clear();
		Selection.Set( this );
	}





	public void OpenAllFunctions()
	{

		var menu = new Menu();
		
	
		var prefabs = AssetSystem.All.Where( x => x.AssetType.FileExtension == "prefab" )
					.Where( x => x.RelativePath.StartsWith( "templates/gameobject/" ) )

					.Select( x => x.LoadResource<PrefabFile>() )
					.Where( x => x.ShowInMenu )
					.OrderByDescending( x => x.MenuPath.Count( x => x == '/' ) )
					.ThenBy( x => x.MenuPath )
					.ToArray();

		foreach ( var entry in prefabs )
		{
			menu.AddOption( entry.MenuPath.Split( '/' ), entry.MenuIcon, () =>
			{
				using var scope = SceneEditorSession.Scope();
				prefabItem = entry;

				PlacerInspector.header.Text = entry.ResourceName;
				PlacerInspector.header.SetStyles( "font-size: 38px; font-weight: 500; font-family: Poppins; color:#B0E24D" );
			} );
		}


		menu.OpenAtCursor();

	}
	
	public override void OnUpdate()
	{


		var tr = Trace
				.UseRenderMeshes( true )
				.UsePhysicsWorld( true )
				.Run();

		if ( !tr.Hit )
		{
			var plane =  new Plane( Vector3.Up, 0f );
			if ( plane.TryTrace( new Ray( tr.StartPosition, tr.Direction ), out tr.EndPosition, true ) )
			{
				tr.Hit = true;
				tr.Normal = plane.Normal;
			}
		}

		if ( tr.Hit )
		{
			if ( EditorScene.GizmoSettings.SnapToGrid )
			{
				tr.EndPosition = tr.EndPosition.SnapToGrid( EditorScene.GizmoSettings.GridSpacing, true, true, true );
			}

			using ( Gizmo.Scope( "tool", new Transform( tr.EndPosition, Rotation.LookAt( tr.Normal ) ) ) )
			{

				Gizmo.Draw.Color = Color.Red;
				Gizmo.Draw.LineCircle( 0, 0.5f );
				Gizmo.Draw.Color = Color.White.WithAlpha( 0.5f );
				Gizmo.Draw.LineCircle( 0, 3 );
				Gizmo.Draw.Color = Color.White.WithAlpha( 0.3f );
				Gizmo.Draw.LineCircle( 0, 6 );
				Gizmo.Draw.Color = Color.White.WithAlpha( 0.2f );
				Gizmo.Draw.LineCircle( 0, 12 );
				Gizmo.Draw.Color = Gizmo.Colors.Blue;

			
				Gizmo.Transform = new Transform( tr.EndPosition + tr.Normal * placeDeltaDistance, Rotation.LookAt( tr.Normal ) );
				Gizmo.Draw.LineBBox( BBox.FromPositionAndSize( 0, 4f ) );
				if ( Gizmo.HasClicked ) AddObject(prefabItem, tr.EndPosition + tr.Normal * placeDeltaDistance, tr);


			}
		}


		
	}

		//adds an empty gameobject at location, should replace it with library later
	void AddObject(PrefabFile entry, Vector3 pos, SceneTraceResult tr)
	{

		using ( Gizmo.Scope( "tool" ) )
		{

		if ( entry != null )
		{
			using var scope = SceneEditorSession.Scope();

			var go = SceneUtility.GetPrefabScene( entry )?.Clone();
			go.BreakFromPrefab();
			go.Name = entry.MenuPath.Split( '/' ).Last();
			
			if ( useSurfaceNormal )
			{
				go.Transform.Local = new Transform( pos, Rotation.LookAt( tr.Normal ));
				Log.Info( tr.Normal );
			}
			else
			{
				go.Transform.Local = new Transform( pos, normalRot );
			}
			if ( selectOnPlace )
			{
				
				//EditorToolManager.SetTool( nameof( ObjectEditorTool ) );
				Selection.Set( go );
	
			}


		}
					
		}
	}


}