So the top surface works but on other sides, it doesn’t position correctly.

local snapValue = 3
local hitPosition = raycastResult.Position
local hitPart = raycastResult.Instance
local snapPosition = Vector3.new(
math.floor(hitPosition.X / snapValue + 0.5) * snapValue,
math.floor(hitPosition.Y / snapValue + 0.5) * snapValue,
math.floor(hitPosition.Z / snapValue + 0.5) * snapValue
)
local surfaceNormal = raycastResult.Normal
local blockOffset = Vector3.new(0, 0, 0)
local newSnapValue = snapValue / 2
if surfaceNormal == Vector3.new(0, 1, 0) then
blockOffset = Vector3.new(0, newSnapValue, 0) -- Place block above the surface
elseif surfaceNormal == Vector3.new(0, -1, 0) then
blockOffset = Vector3.new(0, -newSnapValue, 0) -- Place block below the surface
elseif surfaceNormal == Vector3.new(1, 0, 0) then
blockOffset = Vector3.new(newSnapValue, 0, 0) -- Place block to the right of the surface
elseif surfaceNormal == Vector3.new(-1, 0, 0) then
blockOffset = Vector3.new(-newSnapValue, 0, 0) -- Place block to the left of the surface
elseif surfaceNormal == Vector3.new(0, 0, 1) then
blockOffset = Vector3.new(0, 0, newSnapValue) -- Place block in front of the surface
elseif surfaceNormal == Vector3.new(0, 0, -1) then
blockOffset = Vector3.new(0, 0, -newSnapValue) -- Place block behind the surface
end
VisualBlock.CFrame = CFrame.new(snapPosition + blockOffset)

use math.round not math.floor (if you want to snap to odd parts then you can use this)

function Round(toRound,roundTo) -- toround the number u want to round
-- roundto is the snap value (if u want to snap to a 4x4 stud grid u put 4)
return math.round(toRound/roundTo) * roundTo
end

Theres a problem, the part does not spawn on top of the surface, it spawns half way.

local rayDirection = (characterPrimary.CFrame.LookVector - characterPrimary.CFrame.UpVector * placeBlockAngle).Unit * 8
local raycastResult = workspace:Raycast(rayOrigin, rayDirection, placeBlockRaycastParams)
local surfaceCFrame, surfaceLocalPosition = placer.SnapSurfaceCFrame(raycastResult)
if raycastResult then
local hitPosition = raycastResult.Position
local hitPart = raycastResult.Instance
VisualBlock.CFrame = surfaceCFrame
end

The model handle can be a part or attachment, its CFrame (in world space) is the location you want to use to place the model. It the handle is an attachment, then its the WorldSpaceCFrame property, if its a part, its just that part’s CFrame property.

It should be on the surface you want to place against, so an attachment is preferable. Its rotation matters, and the correction rotation described in the pdf will make it so you can just use the default rotation when an attachment is placed using the studio tool for it.

local raycastResult = workspace:Raycast(rayOrigin, rayDirection, placeBlockRaycastParams)

my old placement code worked but only on the top surface:

local newSnapValue = snapValue / 2
if surfaceNormal == Vector3.new(0, 1, 0) then
blockOffset = Vector3.new(0, newSnapValue, 0) -- Place block above the surface
elseif surfaceNormal == Vector3.new(0, -1, 0) then
blockOffset = Vector3.new(0, -newSnapValue, 0) -- Place block below the surface
elseif surfaceNormal == Vector3.new(1, 0, 0) then
blockOffset = Vector3.new(newSnapValue, 0, 0) -- Place block to the right of the surface
elseif surfaceNormal == Vector3.new(-1, 0, 0) then
blockOffset = Vector3.new(-newSnapValue, 0, 0) -- Place block to the left of the surface
elseif surfaceNormal == Vector3.new(0, 0, 1) then
blockOffset = Vector3.new(0, 0, newSnapValue) -- Place block in front of the surface
elseif surfaceNormal == Vector3.new(0, 0, -1) then
blockOffset = Vector3.new(0, 0, -newSnapValue) -- Place block behind the surface
end

None of this tells me what you expect it to look like. Can you post a picture of where you want the block to be (like as a different color) and where its actually placing it?