Help with rotations

i’m making a simple ledge grabbing system and im trying to make a part place above the player on the top of the object the player wants to grab. but i cant get the rotations to line up.

what i want is for the rotation of the part be perfectly straight to the ledge its on. i kinda feel like this should be easy and im just missing something but i’ve checked through alot of forum posts and im lost

code

local plr = game.Players.LocalPlayer
local char = plr.Character
local hum = char:WaitForChild("Humanoid")
local hrp = char:WaitForChild("HumanoidRootPart")

local run = game:GetService("RunService")

local climbing = false
local holding = false
local goingLeft = false
local goingRight = false
local jumpingUp = false

local uis = game:GetService("UserInputService")

local maxUpThreshold = 5

local checking = false

local visualize = Instance.new("Part")
visualize.Size = Vector3.new(4,1,2)
visualize.Color = Color3.new(1, 0, 0.0156863)
visualize.Transparency = 0.8
visualize.Material = Enum.Material.Glass
visualize.CanCollide = false
visualize.Anchored = true
visualize.Name = "TopLedgeVisualization"	
visualize.Parent = game.Workspace



function CheckIfCanClimbOrHold()
	checking = true
	if checking == true then
	checking = false
	local dir = hrp.CFrame.LookVector * 3
	local org = hrp.Position
	
	local ray = workspace:Raycast(org,dir)
	
	if ray then	

		local y = (ray.Instance.Size.Y/2 + visualize.Size.Y/2)
			
		visualize.CFrame = CFrame.new(hrp.CFrame.Position.X,y + ray.Instance.CFrame.Position.Y ,hrp.CFrame.Position.Z)
			visualize.Rotation = Vector3.new(math.floor(hrp.Rotation / 45 + 0.5) * 45)
		end	
	end
end



while true do
	task.wait()
	uis.InputBegan:Connect(function(inp)
		if inp.KeyCode == Enum.KeyCode.Space then
			CheckIfCanClimbOrHold()		
		end
	end)
end

Calculate the angle between the normal of the surface and the world up vector

function CheckIfCanClimbOrHold()
– …
if ray then
local surfaceNormal = ray.Normal
local worldUp = Vector3.new(0, 1, 0)
local angle = math.acos(surfaceNormal:Dot(worldUp))
local axis = surfaceNormal:Cross(worldUp)
visualize.CFrame = CFrame.new(ray.Position) * CFrame.Angles(axis.X * angle, axis.Y * angle, axis.Z * angle)
end
– …
end

Not quite what i was looking for, first yes it rotates not the right way or on the right axis, also it makes the code that puts the object on the top of the wall/ledge do nothing and instead puts the part infront of the players head.