Get the face a part is welded to

I’m working on a sandbox game where players can spawn various objects and move them using a move tool. The move tool calls workspace:JoinToOutsiders() once they finished moving them to automatically generate welds.

I am working on a “motor” object. The object has a HingeConstraint inside of it and an Attachment, and it waits until any other part gets welded to it. As soon as it gets welded, it creates an Attachment inside the newly-welded part and sets the HingeConstraint up. However the HingeConstraint only works when the part is welded on the correct face.

My question is: How do I get the face the new part is welded to?

First I came up with this abomination:

if
		Weld.C0.Position.Z == first.Size.Z/2
	and Weld.C1.Position.Z == -second.Size.Z/2
then
	print("front")
elseif
		Weld.C0.Position.Z == -first.Size.Z/2
	and Weld.C1.Position.Z == second.Size.Z/2
then
	print("back")
elseif
		Weld.C0.Position.X == first.Size.X/2
	and Weld.C1.Position.X == -second.Size.X/2
then
	print("left")
elseif
		Weld.C0.Position.X == -first.Size.X/2
	and Weld.C1.Position.X == second.Size.X/2
then
	print("right")
elseif
		Weld.C0.Position.Y == -first.Size.Y/2
	and Weld.C1.Position.Y == second.Size.Y/2
then
	print("top")
elseif
		Weld.C0.Position.Y == first.Size.Y/2
	and Weld.C1.Position.Y == -second.Size.Y/2
then
	print("bottom")
else
	warn("unknown")
end

This works but after some testing I realized that it doesn’t account for rotation, so any kind of rotation makes this return an incorrect value and honestly I’m kind of lost at this point. I can’t think of a way to make it work with rotation. I think it could have something to do with converting the Weld’s CFrames to WorldPosition and doing some math then but I have no idea how.

If anyone has experience with this please help, thanks!

You can use this however you can

local function getWeldedFace(weld)
    -- Get the parts involved in the weld
    local part0 = weld.Part0
    local part1 = weld.Part1

    -- Convert Weld CFrames to World Positions
    local weld0WorldPos = part0.CFrame:PointToWorldSpace(weld.C0.Position)
    local weld1WorldPos = part1.CFrame:PointToWorldSpace(weld.C1.Position)

    -- Calculate the relative position of the weld on each part
    local relativePos0 = part0.CFrame:pointToObjectSpace(weld0WorldPos)
    local relativePos1 = part1.CFrame:pointToObjectSpace(weld1WorldPos)

    -- Determine the face based on relative position
    local function getFace(part, relativePos)
        local size = part.Size / 2

        if math.abs(relativePos.X - size.X) < 0.01 then
            return "Right"
        elseif math.abs(relativePos.X + size.X) < 0.01 then
            return "Left"
        elseif math.abs(relativePos.Y - size.Y) < 0.01 then
            return "Top"
        elseif math.abs(relativePos.Y + size.Y) < 0.01 then
            return "Bottom"
        elseif math.abs(relativePos.Z - size.Z) < 0.01 then
            return "Front"
        elseif math.abs(relativePos.Z + size.Z) < 0.01 then
            return "Back"
        else
            return "Unknown"
        end
    end

    local face0 = getFace(part0, relativePos0)
    local face1 = getFace(part1, relativePos1)

    return face0, face1
end

-- Example usage
local weld = -- your weld instance here
local face0, face1 = getWeldedFace(weld)
print("Part0 is welded on its " .. face0 .. " face.")
print("Part1 is welded on its " .. face1 .. " face.")

After a while of experimenting with CFrames I came up with this solution, which works flawlessly:

local function GetNormals(part: BasePart)
	return {
		[Enum.NormalId.Front]   = part.CFrame.LookVector,
		[Enum.NormalId.Back]    = -part.CFrame.LookVector,
		[Enum.NormalId.Right]   = part.CFrame.RightVector,
		[Enum.NormalId.Left]    = -part.CFrame.RightVector,
		[Enum.NormalId.Top]     = part.CFrame.UpVector,
		[Enum.NormalId.Bottom]  = -part.CFrame.UpVector
	}
end

local function ClosestFace(part: BasePart, direction: Vector3)
	local Normals = GetNormals(part)
	local ClosestDot = -math.huge
	
	local Result

	for Face, Normal in next, Normals do
		local Dot = Normal:Dot(direction)
		
		if Dot > ClosestDot then
			ClosestDot = Dot
			Result = Face
		end
	end

	return Result
end

You can then use the ClosestFace function like this:

local Face: Enum.NormalId = ClosestFace(part1, (part1.CFrame.p - part2.Position).Unit)